Merge remote-tracking branch 'upstream/master' into docs

This commit is contained in:
Ben Campbell 2018-04-20 20:24:31 +12:00
commit cb293af5ff
207 changed files with 16295 additions and 2031 deletions

View File

@ -12,7 +12,11 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
# - switch to 3.1.0 features
# the docs say we need to set this up prior to project()
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.8")
# the docs don't say this, but the CACHE part is important; see https://stackoverflow.com/questions/34208360/cmake-seems-to-ignore-cmake-osx-deployment-target
# TODO figure out what other variables must be set with CACHE
# TODO figure out if FORCE is needed here
# TODO figure out whether STRING "" is best or if something else is better; also what FORCE does because I forget and later I say it's needed
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.8" CACHE STRING "" FORCE)
# we want to disable incremental linking
# see also:
@ -118,6 +122,18 @@ else()
-static-libstdc++
)
endif()
# TODO document this
if(ADDRESS_SANITIZER)
set(_COMMON_CFLAGS ${_COMMON_CFLAGS}
-fsanitize=address
-fno-omit-frame-pointer
)
set(_COMMON_LDFLAGS ${_COMMON_LDFLAGS}
-fsanitize=address
-fno-omit-frame-pointer
)
endif()
endif()
# problem:
@ -190,8 +206,9 @@ if(BUILD_SHARED_LIBS)
endif()
macro(_add_exec _name)
# TODOTODO re-add WIN32 when merging back
add_executable(${_name}
WIN32 EXCLUDE_FROM_ALL
EXCLUDE_FROM_ALL
${ARGN})
target_link_libraries(${_name} libui ${_LIBUI_STATIC_RES})
_target_link_options_private(${_name}

131
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,131 @@
# Contributing to libui
libui is an open source project that openly accepts contributions. I appreciate your help!
## Rules for contributing code
While libui is open to contributions, a number of recent, significantly large contributions and uncontributed forks have recently surfaced that do not present themselves in a form that makes it easy for libui to accept them. In order to give your contribution a high chance of being accepted into libui, please keep the following in mind as you prepare your contribution.
### Commit messages and pull request description
libui does not enforce rules about the length or detail that a commit message. I'm not looking for an essay. However, single-word descriptions of nontrivial changes are *not* acceptable. I should be able to get a glimpse of what a commit does from the commit message, even if it's just one sentence to describe a trivial change. (Yes, I know I haven't followed this rule strictly myself, but I try not to break it too.) And a commit message should encompass everything; typically, I make a number of incremental commits toward a feature, so the commit messages don't have to be too long to explain everything.
Your pull request description, on the other hand, must be a summary of the sum total of all the changes made to libui. Don't just drop a pull request on me with a one-line-long elevator pitch of what you added. Describe your proposed API changes, implementation requirements, and any important consequences of your work.
### Code formatting
libui uses K&R C formatting rules for overall code structure: spaces after keywords like `if`, `{` on the same line as a statement with a space, `{` on its own line after a function or method signature (even those inside the class body), no space after the name of a function, etc.
Use hard tabs, NOT spaces, for indentation. I use a proportional-width font and my text editor doesn't set tabs to a multiple of the space width, so I *will* be able to tell. If you use a fixed-width font, I suggest setting a tab width of 4 spaces per tab, but don't put diagrams in comments with hard tabs, because not everyone does this.
Expressions should have a space around binary operators, and use parentheses where it would help humans gather the meaning of an expression, regardless of whether a computer could tell what is correct.
When breaking expressions into multiple lines, always break *after* an operator, such as `,` or `&&`.
There should be a newline between a function's variables and a function's code. After that, you can place newlines to delimit different parts of a function, but don't go crazy.
In the event you are unsure of something, refer to existing libui code for examples. I may wind up fixing minor details later anyway, so don't fret about getting minor details right the first time.
### Naming
libui uses camel-case for naming, with a handful of very specific exceptions (namely GObject method names, where GObject itself enforces the naming convention).
All public API names should begin with `ui` and followed by a capital letter. All public struct field names should begin with a capital letter. This is identical to the visibiilty rules of Go, assuming a package name of `ui`.
Private API names — specifcally those used by more than one source file — should begin with `uipriv` and be followed by a capital letter. This avoids namespace collisions in static libraries.
Static functions and static objects do not have naming restrictions.
Acronyms should **NOT** be mixed-case. `http` for the first word in a camel-case name, `HTTP` for all else, but **NEVER** `Http`. This is possibly the only aspect of the controversial nature of code style that I consider indefensibly stupid.
### API documentation
(TODO I am writing an API documentation tool; once that becomes stable enough I can talk about documenting libui properly. You'll see vestiges of it throughout ui.h, though.)
### Other commenting
(TODO write this part)
### Compatibility
libui takes backward compatibility seriously. Your code should not break the current compatibility requirements. All platforms provide a series of macros, defined in the various `uipriv_*.h` files (or `winapi.hpp` on Windows), that specify the minimum required version. If you find yourself needing to remove these or ignore resultant warnings or errors, you're probably breaking compatibility.
Choosing to drop older versions of Windows, GTK+, and OS X that I could have easily continued to support was not done lightly. If you want to discuss dropping support for an older version of any of these for the benefit of libui, file an issue pleading your case (see below).
GTK+ versions are harder to drop because I am limited by Linux distribution packaging. In general, I will consider bumping GTK+ versions on a new Ubuntu LTS release, choosing the earliest version available on the major distributions at the time of the *previous* Ubuntu LTS release. As of writing, the next milestone will be *after* April 2018, and the target GTK+ version appears to be 3.18, judging by Ubuntu 16.04 LTS alone. This may be bumped back depending on other distros (or it may not be bumped at all), but you may wish to keep this in mind as you write.
(TODO talk about future.c/.cpp/.m files)
As for language compatibility, libui is written in C99. I have no intention of changing this.
As for build system compatibility, libui uses CMake 3.1.0. If you wish to bump the version, file an issue pleading your case (but see below).
**If you do plead your case**, keep in mind that "it's old" is not a sufficient reason to drop things. If you can prove that **virtually no one** uses the minimum version anymore, then that is stronger evidence. The best evidence, however, is that not upgrading will hold libui back in some significant way — but beware that there are some things I won't add to libui itself.
### Windows-specific notes
The Windows backend of libui is written in C++ using C++11.
Despite using C++, please refrain from using the following:
- using C++ in ui_windows.h (this file should still be C compatible)
- smart pointers
- namespaces
- `using namespace`
- ATL, MFC, WTL
The following are not recommended, for consistency with the rest of libui:
- variable declarations anywhere in a function (keep them all at the top)
- `for (int x...` (C++11 foreach syntax is fine, though)
- omitting the `struct` on type names for ordinary structs
The format of a class should be
```c++
class name : public ancestor {
int privateVariable;
// etc.
public:
// public stuff here
};
```
### GTK+-specific notes
Avoid GNU-specific language features. I build with strict C99 conformance.
### OS X-specific notes
Avoid GNU-specific/clang-specific language features. I build with strict C99 conformance.
libui is presently **not** ARC-compliant. Features that require ARC should be avoided for now. I may consider changing this in the future, but it will be a significant change.
To ensure maximum compiler output in the event of a coding error, there should not be any implicit method calls in Objective-C code. For instance, don't do
```objective-c
[[array objectAtIndex:i] method]
```
Instead, cast the result of `objectAtIndex:` to the appropriate type, and then call the method. (TODO learn about, then decide a policy on, soft-generics on things other than `id`)
The format of a class should be
```objective-c
@interface name : parent<protocols> {
// ivars
}
// properties
- (ret)method:(int)arg;
// more methods
@end
@implementation name
- (ret)method:(int)arg
{
// note the lack of semicolon
}
@end
```

View File

@ -1,10 +1,23 @@
# libui: a portable GUI library for C
This README is being written.<br>
[![Build Status](https://travis-ci.org/andlabs/libui.svg)](https://travis-ci.org/andlabs/libui)
[![Build Status](https://travis-ci.org/andlabs/libui.svg?branch=master)](https://travis-ci.org/andlabs/libui)
## Announcements
* **18 April 2018**
* Introduced a new `uiTimer()` function for running code on a timer on the main thread. (Thanks to @cody271.)
* **18 March 2018**
* Introduced an all-new formatted text API that allows you to process formatted text in ways that the old API wouldn't allow. You can read on the whole API [here](https://github.com/andlabs/libui/blob/8944a3fc5528445b9027b1294b6c86bae03eeb89/ui_attrstr.h). There is also a new examples for it: `drawtext`, which shows the whole API at a glance. It doesn't yet support measuring or manipulating text, nor does it currently support functions that would be necessary for things like text editors; all of this will be added back later.
* libui also now uses my [utf library](https://github.com/andlabs/utf) for UTF-8 and UTF-16 processing, to allow consistent behavior across platforms. This usage is not completely propagated throughout libui, but the Windows port uses it in most places now, and eventually this will become what libui will use throughout.
* Also introduced a formal set of contribution guidelines, see `CONTRIBUTING.md` for details. They are still WIP.
* **17 February 2018**
* The longstanding Enter+Escape crashes on Windows have finally been fixed (thanks to @lxn).
* **Alpha 3.5 is now here.** This is a quickie release primiarly intended to deploy the above fix to package ui itself. **It is a partial binary release; sorry!** More new things will come in the next release, which will also introduce semver (so it will be called v0.4.0 instead).
* Alpha 3.5 also includes a new control gallery example. The screenshots below have not been updated yet.
* **27 November 2016**
* Decided to split the table stuff into its own branch. It will be developed independently of everything else, along with a few other features.
@ -42,8 +55,8 @@ This README is being written.<br>
*Note that today's entry (Eastern Time) may be updated later today.*
* **<codedate**
* Added `uiTable` TODO
* **18 April 2018**
* Migrated all code in the `common/` directory to use `uipriv` prefixes for everything that isn't `static`. This is the first step toward fixing static library oddities within libui, allowing libui to truly be safely used as either a static library or a shared library.
* **17 June 2016**
* `uiMainSteps()` no longer takes any arguments and no longer needs to invoke a function to do the work. You still need to call it, but once you do, it will return immediately and you can then get right to your main loop.
@ -147,22 +160,25 @@ Other people have made bindings to other languages:
Language | Bindings
--- | ---
C#/.net | [LibUI.Binding](https://github.com/NattyNarwhal/LibUI.Binding), [SharpUI](https://github.com/benpye/sharpui/)
C++ | [libui-cpp](https://github.com/billyquith/libui-cpp), [cpp-libui-qtlike](https://github.com/aoloe/cpp-libui-qtlike)
C# / .NET Framework | [LibUI.Binding](https://github.com/NattyNarwhal/LibUI.Binding)
C# / .NET Core | [DevZH.UI](https://github.com/noliar/DevZH.UI), [SharpUI](https://github.com/benpye/sharpui/), [LibUISharp](https://github.com/tom-corwin/LibUISharp)
CHICKEN Scheme | [wasamasa/libui](https://github.com/wasamasa/libui)
Crystal | [libui.cr](https://github.com/Fusion/libui.cr)
Crystal | [libui.cr](https://github.com/Fusion/libui.cr), [hedron](https://github.com/Qwerp-Derp/hedron)
D | [DerelictLibui (flat API)](https://github.com/Extrawurst/DerelictLibui), [libuid (object-oriented)](https://github.com/mogud/libuid)
Euphoria | [libui-euphoria](https://github.com/ghaberek/libui-euphoria)
Harbour | [HBUI](https://github.com/RJopek/HBUI)
Haskell | [libui-haskell](https://github.com/ajnsit/libui-haskell), [beijaflor-io/haskell-libui (complete FFI bindings, extensions and higher-level API)](https://github.com/beijaflor-io/haskell-libui)
JavaScript | [libui.js (merged into libui-node?)](https://github.com/mavenave/libui.js)
Haskell | [haskell-libui](https://github.com/beijaflor-io/haskell-libui)
JavaScript | [libui.js (merged into libui-node?)](https://github.com/mavenave/libui.js), [proton-native](https://github.com/kusti8/proton-native)
Julia | [Libui.jl](https://github.com/joa-quim/Libui.jl)
Lua | [libuilua](https://github.com/zevv/libuilua), [libui-lua](https://github.com/mdombroski/libui-lua)
Lua | [libuilua](https://github.com/zevv/libuilua), [libui-lua](https://github.com/mdombroski/libui-lua), [lui](http://tset.de/lui/index.html)
Nim | [ui](https://github.com/nim-lang/ui)
Node.js | [libui-node](https://github.com/parro-it/libui-node)
PHP | [ui](https://github.com/krakjoe/ui)
Python | [pylibui](https://github.com/joaoventura/pylibui)
Python | [pylibui](https://github.com/joaoventura/pylibui), [pylibui-cffi](https://github.com/Yardanico/pylibui-cffi)
Ruby | [libui-ruby](https://github.com/jamescook/libui-ruby)
Rust | [libui-rs](https://github.com/pcwalton/libui-rs)
Scala | [scalaui](https://github.com/lolgab/scalaui)
Swift | [libui-swift](https://github.com/sclukey/libui-swift)
## Frequently Asked Questions
@ -174,6 +190,10 @@ When you run a binary directly from the Terminal, however, you are running it di
See also [this](https://github.com/andlabs/libui/pull/20#issuecomment-211381971) and [this](http://stackoverflow.com/questions/25318524/what-exactly-should-i-pass-to-nsapp-activateignoringotherapps-to-get-my-appl).
## Contributing
See `CONTRIBUTING.md`.
## Screenshots
From examples/controlgallery:

133
TODO.md
View File

@ -127,3 +127,136 @@ label shortcut keys
[02:15:00] <vrishab> 1.40.3
[02:20:46] <andlabs> I'll ahve to keep this in mind then, thanks
[02:20:59] <andlabs> if only there was a cairo-specific attribute for alpha...
FONT LOADING
[00:10:08] <hergertme> andlabs: is there API yet to load from memory? last i checked i only found from file (which we use in builder). https://git.gnome.org/browse/gnome-builder/tree/libide/editor/ide-editor-map-bin.c#n115
[00:13:12] mrmcq2u_ (mrmcq2u@109.79.53.90) joined the channel
[00:14:59] mrmcq2u (mrmcq2u@109.79.73.102) left IRC (Ping timeout: 181 seconds)
[00:15:19] <andlabs> hergertme: no, which is why I was asking =P
[00:15:30] <andlabs> I would have dug down if I could ensure at least something about the backends a GTK+ 3 program uses
[00:15:39] <andlabs> on all platforms except windows and os x
[00:16:11] <hergertme> to the best of my (partially outdated, given pace of foss) knowledge there isn't an api to load from memory
[00:16:28] <hergertme> you can possibly make a tmpdir and put a temp file in there
[00:16:52] <hergertme> and load that as your font dir in your FcConfig, so any PangoFontDescription would point to that one font, no matter what
[00:17:18] <hergertme> (using the API layed out in that link)
[00:18:18] dsr1014__ (dsr1014@c-73-72-102-18.hsd1.il.comcast.net) joined the channel
[00:35:18] simukis_ (simukis@78-60-58-6.static.zebra.lt) left IRC (Quit: simukis_)
[00:35:48] dreamon_ (dreamon@ppp-188-174-49-41.dynamic.mnet-online.de) joined the channel
[00:40:09] samtoday_ (samtoday@114-198-116-132.dyn.iinet.net.au) joined the channel
[00:40:32] mjog (mjog@120.18.225.46) joined the channel
[00:40:38] <andlabs> hergertme: not necessarily fontconfig
[00:40:45] <andlabs> it can be with ft2 or xft I guess
[00:40:55] <andlabs> especially since I want the API NOT to make the font part of the font panel
[00:42:07] <hergertme> what sort of deprecated code are you trying to support?
[00:42:35] <hergertme> both of those are deprecated in pango fwiw
[00:43:06] <hergertme> on Linux im pretty sure we use FC everywhere these days
[00:44:46] <hergertme> (and gtk_widget_set_font_map() is how you get your custom font into a widget without affecting the global font lists, as layed out in that link)
[00:49:14] vasaikar (vasaikar@125.16.97.121) joined the channel
[00:50:14] karlt (karl@2400:e780:801:224:f121:e611:d139:e70e) left IRC (Client exited)
[00:50:49] karlt (karl@2400:e780:801:224:f121:e611:d139:e70e) joined the channel
[00:51:43] PioneerAxon (PioneerAxo@122.171.61.146) left IRC (Ping timeout: 180 seconds)
[00:57:47] PioneerAxon (PioneerAxo@106.201.37.181) joined the channel
[01:03:01] karlt (karl@2400:e780:801:224:f121:e611:d139:e70e) left IRC (Ping timeout: 181 seconds)
[01:05:49] muhannad (muhannad@95.218.26.152) left IRC (Quit: muhannad)
[01:07:51] <andlabs> hergertme: hm
[01:07:54] <andlabs> all right, thanks
[01:08:05] <andlabs> hergertme: fwiw right now my requirement is 3.10
[01:10:47] <hergertme> ah, well you'll probably be missing the neccesary font API on gtk_widget
[01:11:04] <hergertme> but pango should be fine even back as far as https://developer.gnome.org/pango/1.28/PangoFcFontMap.html
[01:11:56] <andlabs> good
[01:12:04] <andlabs> because this is for custom drawing into a DrawingArea
[01:14:12] <hergertme> presumably just create your PangoContext as normal, but call pango_context_set_font_map() with the map you've setup. now, the load a font from a file i dont think was added to FontConfig until later though (not sure what release)
[01:15:53] <hergertme> FcConfigAppFontAddFile() <-- that API
[01:16:30] <hergertme> great, and they don't say what version the API was added in teh docs
function: ide_editor_map_bin_add()
- Mouse ClickLock: do we need to do anything special? *should* we? https://msdn.microsoft.com/en-us/library/windows/desktop/ms724947(v=vs.85).aspx
- consider a uiAnticipateDoubleClick() or uiDoubleClickTime() (for a uiQueueTimer()) or something: https://blogs.msdn.microsoft.com/oldnewthing/20041015-00/?p=37553
- determine whether MSGF_USER is for and if it's correct for our uiArea message filter (if we have one)
- source file encoding and MSVC compiler itself? https://stackoverflow.com/questions/20518040/how-can-i-get-the-directwrite-padwrite-sample-to-work
- also need to worry about object file and output encoding...
- this also names the author of the padwrite sample
- OpenType features TODOs
- https://stackoverflow.com/questions/32545675/what-are-the-default-typography-settings-used-by-idwritetextlayout
- feature/shaping interaction rules for arabic: https://www.microsoft.com/typography/OpenTypeDev/arabic/intro.htm
- other stuff, mostly about UIs and what users expect to be able to set
- https://klim.co.nz/blog/towards-an-ideal-opentype-user-interface/
- https://libregraphicsmeeting.org/2016/designing-for-many-applications-opentype-features-ui/
- https://www.youtube.com/watch?v=wEyDhsH076Y
- https://twitter.com/peter_works
- http://ilovetypography.com/2014/10/22/better-ui-for-better-typography-adobe-petition/
- http://silgraphite.sourceforge.net/ui/studynote.html
- add NXCOMPAT (DEP awareness) to the Windows builds
- and ASLR too? or is that not a linker setting
OS X: embedding an Info.plist into a binary directly
https://www.objc.io/issues/6-build-tools/mach-o-executables/
TODO will this let Dictation work?
TODO investigate ad-hoc codesigning
https://blogs.msdn.microsoft.com/oldnewthing/20040112-00/?p=41083 def files for decoration (I forget if I said this earlier)
TODO ClipCursor() stuff; probably not useful for libui but still
https://blogs.msdn.microsoft.com/oldnewthing/20140102-00/?p=2183
https://blogs.msdn.microsoft.com/oldnewthing/20061117-03/?p=28973
https://msdn.microsoft.com/en-us/library/windows/desktop/ms648383(v=vs.85).aspx
https://cmake.org/Wiki/CMake_Useful_Variables
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined")
On Unix systems, this will make linker report any unresolved symbols from object files (which is quite typical when you compile many targets in CMake projects, but do not bother with linking target dependencies in proper order).
(I used to have something like this back when I used makefiles; did it convert in? I forget)
look into these for the os x port
https://developer.apple.com/documentation/appkit/view_management/nseditor?language=objc
https://developer.apple.com/documentation/appkit/view_management/nseditorregistration?language=objc
for future versions of the os x port
https://developer.apple.com/documentation/appkit/nslayoutguide?language=objc and anchors
https://developer.apple.com/documentation/appkit/nsuserinterfacecompression?language=objc https://developer.apple.com/documentation/appkit/nsuserinterfacecompressionoptions?language=objc
though at some point we'll be able to use NSStackView and NSGridView directly, so...
Cocoa PDFs
https://developer.apple.com/documentation/appkit/nspdfimagerep?language=objc
https://developer.apple.com/documentation/coregraphics?language=objc
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_pagination/osxp_pagination.html#//apple_ref/doc/uid/20001051-119037
https://developer.apple.com/documentation/appkit/nsprintoperation/1529269-pdfoperationwithview?language=objc
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printapps/osxp_printapps.html#//apple_ref/doc/uid/20000861-BAJBFGED
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printingapi/osxp_printingapi.html#//apple_ref/doc/uid/10000083i-CH2-SW2
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printinfo/osxp_printinfo.html#//apple_ref/doc/uid/20000864-BAJBFGED
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printlayoutpanel/osxp_printlayoutpanel.html#//apple_ref/doc/uid/20000863-BAJBFGED
https://developer.apple.com/documentation/appkit/nspagelayout?language=objc
https://developer.apple.com/documentation/appkit/nsprintinfo?language=objc
https://developer.apple.com/documentation/applicationservices/core_printing?language=objc
https://developer.apple.com/documentation/applicationservices/1463247-pmcreatesession?language=objc
https://developer.apple.com/documentation/applicationservices/pmprintsession?language=objc
https://developer.apple.com/documentation/applicationservices/1460101-pmsessionbegincgdocumentnodialog?language=objc
https://developer.apple.com/documentation/applicationservices/1463416-pmsessionbeginpagenodialog?language=objc
https://developer.apple.com/documentation/applicationservices/1506831-anonymous/kpmdestinationprocesspdf?language=objc
https://developer.apple.com/documentation/applicationservices/1461960-pmcreategenericprinter?language=objc
https://developer.apple.com/documentation/applicationservices/1460101-pmsessionbegincgdocumentnodialog?language=objc
https://developer.apple.com/documentation/applicationservices/1464527-pmsessionenddocumentnodialog?language=objc
https://developer.apple.com/documentation/applicationservices/1461952-pmsessiongetcggraphicscontext?language=objc
https://developer.apple.com/library/content/technotes/tn2248/_index.html
https://developer.apple.com/library/content/samplecode/PMPrinterPrintWithFile/Introduction/Intro.html
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_aboutprinting/osxp_aboutprt.html
- run os x code with `OBJC_DEBUG_MISSING_POOLS=YES` and other `OBJC_HELP=YES` options
- turn off the autorelease pool to make sure we're not autoreleasing improperly
TODO investigate -Weverything in clang alongside -Wall in MSVC (and in gcc too maybe...)
mac os x accessibility
- https://developer.apple.com/documentation/appkit/nsworkspace/1524656-accessibilitydisplayshoulddiffer?language=objc
- https://developer.apple.com/documentation/appkit/nsworkspace/1526290-accessibilitydisplayshouldincrea?language=objc
- https://developer.apple.com/documentation/appkit/nsworkspace/1533006-accessibilitydisplayshouldreduce?language=objc
uiEntry disabling bugs http://www.cocoabuilder.com/archive/cocoa/215525-nstextfield-bug-can-be.html
uiMultilineEntry disabling https://developer.apple.com/library/content/qa/qa1461/_index.html
more TODOs:
- make no guarantee about buildability of feature branches

View File

@ -0,0 +1,190 @@
_UI_ENUM(uiAttribute) {
uiAttributeFamily,
uiAttributeSize, // use Double
uiAttributeWeight,
uiAttributeItalic,
uiAttributeStretch,
uiAttributeColor, // use R, G, B, A
uiAttributeBackground, // use R, G, B, A
// TODO kerning amount
// OS X: kCTKernAttributeName
// > 0: farther (TODO from advance or standard kerning?)
// == 0: no kerning
// < 0: closer (TODO same)
// undefined: standard kerning
// Pango: pango_attr_letter_spacing_new()
// parameter meaning unspecified
// Windows: requires Platform Update, SetLetterSpacing()
// parameter meaning unspecified
uiAttributeUnderline, // enum uiDrawUnderlineStyle
// TODO what is the color in the case we don't specify it, black or the text color?
uiAttributeUnderlineColor, // enum uiDrawUnderlineColor
// TODO kCTSuperscriptAttributeName vs below
// all it does is set the below attribute so
// TODO kCTBaselineClassAttributeName, kCTBaselineInfoAttributeName, kCTBaselineReferenceInfoAttributeName
// TODO strikethroughs? (pango yes, directwrite yes, os x no)
// TODO baseline offsets? (pango yes)
// TODO size scales? (pango yes)
// TODO fallbacks (pango: enable or disable)
// TODO document that this will also enable language-specific font features (TODO on DirectWrite too?)
// TODO document that this should be strict BCP 47 form (A-Z, a-z, 0-9, and -) for maximum compatibility
uiAttributeLanguage, // BCP 47 string
// These attributes represent typographic features. Each feature
// is a separate attribute, to make composition easier. The
// availability of for each attribute are defined by the font; the
// default values are defined by the font and/or by the OS.
//
// A note about features whose parameter is an enumeration:
// OS X defines typographic features using the AAT specification
// and converts to OpenType internally when needed, whereas
// other platforms use OpenType directly. OpenType is less
// precise about what each enumeration value means than AAT
// is, so enumeration values do not necessarily represent what
// OS X expects with all fonts. In cases where they do, libui
// provides an enumeration type to use. Otherwise, the AAT
// enumeration values are provided in comments for
// documentation purposes.
// TODO kAllTypographicFeaturesType
// AAT calls these "common ligatures"
uiAttributeStandardLigatures, // 0 = off, 1 = on
uiAttributeRequiredLigatures, // 0 = off, 1 = on
// AAT calls these "rare ligatures"
uiAttributeDiscretionaryLigatures, // 0 = off, 1 = on
uiAttributeContextualLigatures, // 0 = off, 1 = on
uiAttributeHistoricalLigatures, // 0 = off, 1 = on
// TODO uiAttributeCursiveConnection, // 0 = none, 1 = some, 2 = all
uiAttributeUnicase, // 0 = off, 1 = on
// TODO uiAttributeLinguisticRearrangement, // 0 = off, 1 = on
// TODO rename this
uiAttributeNumberSpacings, // enum uiAttributeNumberSpacing
// TODO kSmartSwashType, falt and jalt
// TODO kDiacriticsType
uiAttributeSuperscripts, // enum uiAttributeSuperscript
uiAttributeFractionForms, // enum uiAttributeFractionForm
uiAttributeSlashedZero, // 0 = off, 1 = on
uiAttributeMathematicalGreek, // 0 = off, 1 = on
// AAT defines the following values:
// 0 = none
// 1 = dingbats
// 2 = pi characters
// 3 = fleurons
// 4 = decorative borders
// 5 = international symbols
// 6 = mathematical symbols
// OpenType says alphanumeric characters must(? TODO) have one form each and the bullet character U+2022 (•) can have many
uiAttributeOrnamentalForms, // an integer from 0 to a font-specified upper bound
// TODO provide a function to get the upper bound?
// AAT calls this "character alternatives" and defines the
// following values:
// 0 = none
// OpenType calls this "access all alternates".
// TODO doesn't OpenType do the same about 0?
uiAttributeSpecificCharacterForm, // an integer from 0 to a font-specified upper bound
// TODO provide a function to get the upper bound?
uiAttributeTitlingCapitalForms, // 0 = off, 1 = on
// AAT calls these "character shapes"
uiAttributeHanCharacterForms, // enum uiAttributeHanCharacterForm
// OpenType calls these "old-style"
uiAttributeLowercaseNumbers, // 0 = off, 1 = on
// TODO kTextSpacingType
// see kKanaSpacingType below
uiAttributeHanjaToHangul, // 0 = off, 1 = on
// AAT defines the following values:
// 0 = none
// 1 = box
// 2 = rounded box
// 3 = circle
// 4 = inverted circle
// 5 = parentheses
// 6 = period
// 7 = roman numeral
// 8 = diamond
// 9 = inverted box
// 10 = inverted rounded box
// TODO rename to AnnotatedForms?
uiAttributeAnnotatedGlyphForms, // an integer from 0 to a font-specified upper bound
// TODO provide a function to get the upper bound?
// TODO kKanaSpacingType
// TODO kIdeographicSpacingType
// can they be provided independently of kTextSpacingType? Core Text doesn't seem to
// TODO kUnicodeDecompositionType
uiAttributeRubyKanaForms, // 0 = off, 1 = on
// TODO kCJKVerticalRomanPlacementType
// this is 'valt' in OpenType but I don't know if I want to make it selectable or not
uiAttributeCJKRomansToItalics, // 0 = off, 1 = on
// AAT calls this "case-sensitive layout"
uiAttributeCaseSensitiveForms, // 0 = off, 1 = on
// AAT: this is called "case-sensitive spacing"
uiAttributeCapitalSpacing, // 0 = off, 1 = on
uiAttributeAlternateHorizontalKana, // 0 = off, 1 = on
uiAttributeAlternateVerticalKana, // 0 = off, 1 = on
// TODO "Alternate"? unify all this
// TODO document that these are guaranteed to be consecutive
uiAttributeStylisticAlternate1, // 0 = off, 1 = on
uiAttributeStylisticAlternate2, // 0 = off, 1 = on
uiAttributeStylisticAlternate3, // 0 = off, 1 = on
uiAttributeStylisticAlternate4, // 0 = off, 1 = on
uiAttributeStylisticAlternate5, // 0 = off, 1 = on
uiAttributeStylisticAlternate6, // 0 = off, 1 = on
uiAttributeStylisticAlternate7, // 0 = off, 1 = on
uiAttributeStylisticAlternate8, // 0 = off, 1 = on
uiAttributeStylisticAlternate9, // 0 = off, 1 = on
uiAttributeStylisticAlternate10, // 0 = off, 1 = on
uiAttributeStylisticAlternate11, // 0 = off, 1 = on
uiAttributeStylisticAlternate12, // 0 = off, 1 = on
uiAttributeStylisticAlternate13, // 0 = off, 1 = on
uiAttributeStylisticAlternate14, // 0 = off, 1 = on
uiAttributeStylisticAlternate15, // 0 = off, 1 = on
uiAttributeStylisticAlternate16, // 0 = off, 1 = on
uiAttributeStylisticAlternate17, // 0 = off, 1 = on
uiAttributeStylisticAlternate18, // 0 = off, 1 = on
uiAttributeStylisticAlternate19, // 0 = off, 1 = on
uiAttributeStylisticAlternate20, // 0 = off, 1 = on
uiAttributeContextualAlternates, // 0 = off, 1 = on
uiAttributeSwashes, // 0 = off, 1 = on
uiAttributeContextualSwashes, // 0 = off, 1 = on
uiAttributeLowercaseCapForms, // enum uiAttributeCapForm
uiAttributeUppercaseCapForms, // enum uiAttributeCapForm
// TODO kCJKRomanSpacingType
// TODO uiAttributeSystem, (this might not be doable with DirectWrite)
// TODO uiAttributeCustom,
};

View File

@ -0,0 +1 @@
Removed because proper support on OS X doesn't come until 10.9 unless we use a font with an ltag table; none of the fonts I have come with ltag tables (none of the fonts on OS X do, or at least don't come with a sr entry in their ltag table, and OpenType has replaced ltag with what appears to be custom sub-tables of the GPOS and GSUB tables.)

View File

@ -0,0 +1,19 @@
struct fontParams {
uiDrawFontDescriptor desc;
uint16_t featureTypes[maxFeatures];
uint16_t featureSelectors[maxFeatures];
size_t nFeatures;
const char *language;
};
// locale identifiers are specified as BCP 47: https://developer.apple.com/reference/corefoundation/cflocale?language=objc
case uiAttributeLanguage:
// LONGTERM FUTURE when we move to 10.9, switch to using kCTLanguageAttributeName
ensureFontInRange(p, start, end);
adjustFontInRange(p, start, end, ^(struct fontParams *fp) {
fp->language = (const char *) (spec->Value);
});
break;
desc = fontdescAppendFeatures(desc, fp->featureTypes, fp->featureSelectors, fp->nFeatures, fp->language);

View File

@ -0,0 +1,9 @@
PangoLanguage *lang;
// language strings are specified as BCP 47: https://developer.gnome.org/pango/1.30/pango-Scripts-and-Languages.html#pango-language-from-string https://www.ietf.org/rfc/rfc3066.txt
case uiAttributeLanguage:
lang = pango_language_from_string((const char *) (spec->Value));
addattr(p, start, end,
pango_attr_language_new(lang));
// lang *cannot* be freed
break;

View File

@ -0,0 +1,10 @@
WCHAR *localeName;
// locale names are specified as BCP 47: https://msdn.microsoft.com/en-us/library/windows/desktop/dd373814(v=vs.85).aspx https://www.ietf.org/rfc/rfc4646.txt
case uiAttributeLanguage:
localeName = toUTF16((char *) (spec->Value));
hr = p->layout->SetLocaleName(localeName, range);
if (hr != S_OK)
logHRESULT(L"error applying locale name attribute", hr);
uiFree(localeName);
break;

View File

@ -0,0 +1,2 @@
case uiAttributeLanguage:
return asciiStringsEqualCaseFold((char *) (attr->spec.Value), (char *) (spec->Value));

View File

@ -0,0 +1,27 @@
before "or any combination of the above"
// thanks to https://twitter.com/codeman38/status/831924064012886017
next = "\xD0\xB1\xD0\xB3\xD0\xB4\xD0\xBF\xD1\x82";
uiAttributedStringAppendUnattributed(attrstr, "multiple languages (compare ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeItalic;
spec.Value = uiDrawTextItalicItalic;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
spec.Type = uiAttributeLanguage;
spec.Value = (uintptr_t) "ru";
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " to ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeItalic;
spec.Value = uiDrawTextItalicItalic;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
spec.Type = uiAttributeLanguage;
spec.Value = (uintptr_t) "sr";
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " \xE2\x80\x94 may require changing the font)");
uiAttributedStringAppendUnattributed(attrstr, ", ");

View File

@ -0,0 +1,112 @@
// note: this doesn't work for languages; we have to parse the ltag table
// fortunately features that aren't supported are simply ignored, so we can copy them all in
// LONGTERM FUTURE when we switch to 10.9, the language parameter won't be needed anymore
// LONGTERM FUTURE and on 10.10 we can use OpenType tags directly!
CTFontDescriptorRef fontdescAppendFeatures(CTFontDescriptorRef desc, const uint16_t *types, const uint16_t *selectors, size_t n, const char *language)
{
CTFontDescriptorRef new;
CFMutableArrayRef outerArray;
CFDictionaryRef innerDict;
CFNumberRef numType, numSelector;
const void *keys[2], *values[2];
size_t i;
CFArrayRef languages;
CFIndex il, nl;
CFStringRef curlang;
char d[2];
outerArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
if (outerArray == NULL) {
// TODO
}
keys[0] = kCTFontFeatureTypeIdentifierKey;
keys[1] = kCTFontFeatureSelectorIdentifierKey;
for (i = 0; i < n; i++) {
numType = CFNumberCreate(NULL, kCFNumberSInt16Type,
(const SInt16 *) (types + i));
numSelector = CFNumberCreate(NULL, kCFNumberSInt16Type,
(const SInt16 *) (selectors + i));
values[0] = numType;
values[1] = numSelector;
innerDict = CFDictionaryCreate(NULL,
keys, values, 2,
// TODO are these correct?
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (innerDict == NULL) {
// TODO
}
CFArrayAppendValue(outerArray, innerDict);
CFRelease(innerDict);
CFRelease(numSelector);
CFRelease(numType);
}
// now we have to take care of the language
if (language != NULL) {
languages = CTFontDescriptorCopyAttribute(desc, kCTFontLanguagesAttribute);
if (languages != NULL) {
nl = CFArrayGetCount(languages);
d[0] = language[0];
if (d[0] >= 'A' && d[0] <= 'Z')
d[0] += 'a' - 'A';
d[1] = language[1];
if (d[1] >= 'A' && d[1] <= 'Z')
d[1] += 'a' - 'A';
for (il = 0; il < nl; il++) {
char c[2];
curlang = (CFStringRef) CFArrayGetValueAtIndex(languages, il);
// TODO check for failure
CFStringGetBytes(curlang, CFRangeMake(0, 2),
kCFStringEncodingUTF8, 0, false,
(UInt8 *) c, 2, NULL);
if (c[0] >= 'A' && c[0] <= 'Z')
c[0] += 'a' - 'A';
if (c[1] >= 'A' && c[1] <= 'Z')
c[1] += 'a' - 'A';
if (c[0] == d[0] && c[1] == d[1])
break;
}
if (il != nl) {
uint16_t typ;
typ = kLanguageTagType;
il++;
numType = CFNumberCreate(NULL, kCFNumberSInt16Type,
(const SInt16 *) (&typ));
numSelector = CFNumberCreate(NULL, kCFNumberCFIndexType,
&il);
values[0] = numType;
values[1] = numSelector;
innerDict = CFDictionaryCreate(NULL,
keys, values, 2,
// TODO are these correct?
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (innerDict == NULL) {
// TODO
}
CFArrayAppendValue(outerArray, innerDict);
CFRelease(innerDict);
CFRelease(numSelector);
CFRelease(numType);
}
CFRelease(languages);
}
}
keys[0] = kCTFontFeatureSettingsAttribute;
values[0] = outerArray;
innerDict = CFDictionaryCreate(NULL,
keys, values, 1,
// TODO are these correct?
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFRelease(outerArray);
new = CTFontDescriptorCreateCopyWithAttributes(desc, innerDict);
CFRelease(desc);
CFRelease(innerDict);
return new;
}

View File

@ -0,0 +1,5 @@
after UnderlineColor, before feature tags
// TODO document that this will also enable language-specific font features (TODO on DirectWrite too?)
// TODO document that this should be strict BCP 47 form (A-Z, a-z, 0-9, and -) for maximum compatibility
uiAttributeLanguage, // BCP 47 string

View File

@ -0,0 +1,25 @@
= attributed strings
attribute lengths are rounded to complete unicode codepoints
zero-length attributes are elided
consecutive attributes of the same type and value are merged
overlapping attributes of different types do not split each other
overlapping attributes of the same type but different values do split
empty string is allowed
empty string cannot have attributes
font family names are case-insensitive both in attributes and in descriptors
attributes are unique throughout a Unicode codepoint, not just to UTF-8 bytes
define what "it is an error" means in the case of uiFreeAttribute() and all uiAttributeValue() functions and constructors
does uiAttributeFamily() return a normalized string
should uiNewAttributeBackground() be renamed to uiNewAttributeBackgroundColor() and likewise for the type constant
should underline colors just ignore non-custom component arguments
should any color getter function accept a NULL pointer
what should uiAttributeUnderlineColor() do if the color type isn't Custom but the other pointers are non-NULL
should uiOpenTypeFeaturesGet() accept a NULL value pointer
what happens if uiOpenTypeFeaturesForEach() is given a NULl function pointer
should FeaturesAttribute be changed to OpenTypeFeaturesAttribute and likewise for the type enum
should uiNewFeaturesAttribute() accept NULL
should uiNewFamilyAttribute() accept NULL
it is an error in ForEach too
invalid values for uiDrawTextAlign
empty text layouts have one line
TODO figure out what to do if any field (particularly the font family name) in uiFontDescriptor is unset

View File

@ -0,0 +1,115 @@
// 27 february 2018
#ifndef TODO_TEST
#error TODO this is where libui itself goes
#endif
#include <inttypes.h>
#include "testing.h"
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
typedef struct uiOpenTypeFeatures uiOpenTypeFeatures;
typedef int uiForEach;
enum { uiForEachContinue, uiForEachStop };
typedef uiForEach (*uiOpenTypeFeaturesForEachFunc)(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data);
#define uiprivNew(x) ((x *) malloc(sizeof (x)))
#define uiprivAlloc(x,y) malloc(x)
#define uiprivRealloc(x,y,z) realloc(x,y)
#define uiprivFree free
#include "opentype.c"
static void freeOpenType(void *otf)
{
uiFreeOpenTypeFeatures((uiOpenTypeFeatures *) otf);
}
testingTest(OpenTypeFeaturesAddGet)
{
uiOpenTypeFeatures *otf;
int got;
uint32_t value;
otf = uiNewOpenTypeFeatures();
testingTDefer(t, freeOpenType, otf);
uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);
got = uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value);
if (!got)
testingTErrorf(t, "uiOpenTypeFeaturesGet() failed to get feature we added");
else if (value != 12345)
testingTErrorf(t, "feature abcd: got %" PRIu32 ", want 12345", value);
}
testingTest(OpenTypeFeaturesRemove)
{
uiOpenTypeFeatures *otf;
uint32_t value;
otf = uiNewOpenTypeFeatures();
testingTDefer(t, freeOpenType, otf);
uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);
uiOpenTypeFeaturesRemove(otf, 'a', 'b', 'c', 'd');
if (uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value))
testingTErrorf(t, "uiOpenTypeFeaturesGet() succeeded in getting deleted feature; value %" PRIu32, value);
}
testingTest(OpenTypeFeaturesCloneAdd)
{
uiOpenTypeFeatures *otf, *otf2;
uint32_t value;
otf = uiNewOpenTypeFeatures();
testingTDefer(t, freeOpenType, otf);
uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);
otf2 = uiOpenTypeFeaturesClone(otf);
testingTDefer(t, freeOpenType, otf2);
uiOpenTypeFeaturesAdd(otf2, 'q', 'w', 'e', 'r', 56789);
if (uiOpenTypeFeaturesGet(otf, 'q', 'w', 'e', 'r', &value))
testingTErrorf(t, "uiOpenTypeFeaturesGet() on original succeeded in getting feature added to clone; value %" PRIu32, value);
}
testingTest(OpenTypeFeaturesCloneModify)
{
uiOpenTypeFeatures *otf, *otf2;
uint32_t value;
otf = uiNewOpenTypeFeatures();
testingTDefer(t, freeOpenType, otf);
uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);
otf2 = uiOpenTypeFeaturesClone(otf);
testingTDefer(t, freeOpenType, otf2);
uiOpenTypeFeaturesAdd(otf2, 'a', 'b', 'c', 'd', 56789);
uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value);
if (value != 12345)
testingTErrorf(t, "uiOpenTypeFeaturesGet() on original: got %" PRIu32 ", want 12345", value);
uiOpenTypeFeaturesGet(otf2, 'a', 'b', 'c', 'd', &value);
if (value != 56789)
testingTErrorf(t, "uiOpenTypeFeaturesGet() on clone: got %" PRIu32 ", want 56789", value);
}
testingTest(OpenTypeFeaturesCloneRemove)
{
uiOpenTypeFeatures *otf, *otf2;
uint32_t value;
otf = uiNewOpenTypeFeatures();
testingTDefer(t, freeOpenType, otf);
uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);
otf2 = uiOpenTypeFeaturesClone(otf);
testingTDefer(t, freeOpenType, otf2);
uiOpenTypeFeaturesRemove(otf2, 'a', 'b', 'c', 'd');
if (uiOpenTypeFeaturesGet(otf2, 'a', 'b', 'c', 'd', &value))
testingTErrorf(t, "uiOpenTypeFeaturesGet() on clone succeeded in getting feature removed from clone; value %" PRIu32, value);
if (!uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value))
testingTErrorf(t, "uiOpenTypeFeaturesGet() on original failed to get feature removed from clone");
}
int main(void)
{
return testingMain();
}

133
_future/unittest/testing.h Normal file
View File

@ -0,0 +1,133 @@
// 27 february 2018
#ifndef testingprivIncludeGuard_testing_h
#define testingprivIncludeGuard_testing_h
#include <stdarg.h>
#undef testingprivBadLanguageVersion
#ifdef __cplusplus
// TODO https://stackoverflow.com/questions/2324658/how-to-determine-the-version-of-the-c-standard-used-by-the-compiler implies this won't do with C++0x-era compilers, and https://wiki.apache.org/stdcxx/C++0xCompilerSupport doesn't talk about va_copy() so a simple version check for the C99 preprocessor may be wrong...
// TODO what if __cplusplus is blank (maybe only in that case, since IIRC C++98 requires __cplusplus to have a value)?
#if __cplusplus < 201103L
#define testingprivBadLanguageVersion
#endif
#elif !defined(__STDC_VERSION__)
#define testingprivBadLanguageVersion
#elif __STDC_VERSION__ < 199901L
#define testingprivBadLanguageVersion
#endif
#ifdef testingprivBadLanguageVersion
#error sorry, TODO requires either C99 or C++11; cannot continue
#endif
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
#define testingprivMkScaffold(name) \
static inline void testingprivScaffold ## name(testingT *t) \
{ \
bool failedNow = false, skippedNow = false; \
try { name(t); } \
catch (testingprivFailNowException e) { failedNow = true; } \
catch (testingprivSkipNowException e) { skippedNow = true; } \
/* TODO see if we should catch other exceptions too */ \
/* don't call these in the catch blocks as they call longjmp() */ \
if (failedNow) testingprivTDoFailNow(t); \
if (skippedNow) testingprivTDoSkipNow(t); \
}
#else
#define testingprivMkScaffold(name) \
static inline void testingprivScaffold ## name(testingT *t) { name(t); }
#endif
// references:
// - https://gitlab.gnome.org/GNOME/glib/blob/master/glib/gconstructor.h
// - https://gitlab.gnome.org/GNOME/glib/blob/master/gio/glib-compile-resources.c
// - https://msdn.microsoft.com/en-us/library/bb918180.aspx
#if defined(__cplusplus)
#define testingprivMkCtor(name, reg) \
static reg ## Class testingprivCtor ## name(#name, testingprivScaffold ## name);
#elif defined(__GNUC__)
#define testingprivMkCtor(name, reg) \
__attribute__((constructor)) static void testingprivCtor ## name(void) { reg(#name, testingprivScaffold ## name); }
#elif defined(_MSC_VER)
#define testingprivMkCtorPrototype(name, reg) \
static int name(void) testingprivCtor ## name(void) { reg(#name, testingprivScaffold ## name); return 0; } \
__pragma(section(".CRT$XCU",read)) \
__declspec(allocate(".CRT$XCU")) static int (*testingprivCtorPtr ## name)(void) = testingprivCtor ## name;
#elif defined(__SUNPRO_C)
#define testingprivMkCtor(name, reg) \
_Pragma("init(testingprivCtor" #name ")") static void testingprivCtor ## name(void) { reg(#name, testingprivScaffold ## name); }
#else
#error unknown compiler for making constructors in C; cannot continue
#endif
#define testingTest(Name) \
void Test ## Name(testingT *t); \
testingprivMkScaffold(Test ## Name) \
testingprivMkCtor(Test ## Name, testingprivRegisterTest) \
void Test ## Name(testingT *t)
extern int testingMain(void);
typedef struct testingT testingT;
#define testingTLogf(t, ...) \
testingprivExpand(testingprivTLogfThen((void), t, __VA_ARGS__))
#define testingTLogvf(t, format, ap) \
testingprivTLogvfThen((void), t, format, ap)
#define testingTErrorf(t, ...) \
testingprivExpand(testingprivTLogfThen(testingTFail, t, __VA_ARGS__))
#define testingTErrorvf(t, format, ap) \
testingprivTLogvfThen(testingTFail, t, format, ap)
#define testingTFatalf(t, ...) \
testingprivExpand(testingprivTLogfThen(testingTFailNow, t, __VA_ARGS__))
#define testingTFatalvf(t, format, ap) \
testingprivTLogvfThen(testingTFailNow, t, format, ap)
#define testingTSkipf(t, ...) \
testingprivExpand(testingprivTLogfThen(testingTSkipNow, t, __VA_ARGS__))
#define testingTSkipvf(t, format, ap) \
testingprivTLogvfThen(testingTSkipNow, t, format, ap)
extern void testingTFail(testingT *t);
#ifdef __cplusplus
#define testingTFailNow(t) (throw testingprivFailNowException())
#define testingTSkipNow(t) (throw testingprivSkipNowException())
#else
#define testingTFailNow(t) (testingprivTDoFailNow(t))
#define testingTSkipNow(t) (testingprivTDoSkipNow(t))
#endif
// TODO should the defered function also have t passed to it?
extern void testingTDefer(testingT *t, void (*f)(void *data), void *data);
// TODO IEEE 754 helpers
// references:
// - https://www.sourceware.org/ml/libc-alpha/2009-04/msg00005.html
// - https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
// - https://stackoverflow.com/questions/5085533/is-a-c-preprocessor-identical-to-a-c-preprocessor
// TODO should __LINE__ arguments use intmax_t or uintmax_t instead of int?
extern void testingprivRegisterTest(const char *, void (*)(testingT *));
// see https://stackoverflow.com/questions/32399191/va-args-expansion-using-msvc
#define testingprivExpand(x) x
#define testingprivTLogfThen(then, t, ...) ((testingprivTLogfFull(t, __FILE__, __LINE__, __VA_ARGS__)), (then(t)))
#define testingprivTLogvfThen(then, t, format, ap) ((testingprivTLogvfFull(t, __FILE__, __LINE__, format, ap)), (then(t)))
extern void testingprivTLogfFull(testingT *, const char *, int, const char *, ...);
extern void testingprivTLogvfFull(testingT *, const char *, int, const char *, va_list);
extern void testingprivTDoFailNow(testingT *);
extern void testingprivTDoSkipNow(testingT *);
#ifdef __cplusplus
}
namespace {
class testingprivFailNowException {};
class testingprivSkipNowException {};
class testingprivRegisterTestClass {
public:
testingprivRegisterTestClass(const char *name, void (*f)(testingT *)) { testingprivRegisterTest(name, f); }
};
}
#endif
#endif

View File

@ -0,0 +1,144 @@
// 27 february 2018
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include "testing.h"
#define testingprivNew(T) ((T *) malloc(sizeof (T)))
struct defer {
void (*f)(void *);
void *data;
struct defer *next;
};
struct testingT {
const char *name;
void (*f)(testingT *);
int failed;
int skipped;
jmp_buf returnNowBuf;
struct defer *defers;
int defersRun;
testingT *next;
};
static testingT *tests = NULL;
void testingprivRegisterTest(const char *name, void (*f)(testingT *))
{
testingT *t;
t = testingprivNew(testingT);
t->name = name;
t->f = f;
t->failed = 0;
t->skipped = 0;
t->defers = NULL;
t->defersRun = 0;
// TODO add in the order called
t->next = tests;
tests = t;
}
static void runDefers(testingT *t)
{
struct defer *d;
if (t->defersRun)
return;
t->defersRun = 1;
for (d = t->defers; d != NULL; d = d->next)
(*(d->f))(d->data);
}
int testingMain(void)
{
testingT *t;
int anyFailed;
const char *status;
// TODO see if this should run if all tests are skipped
if (tests == NULL) {
fprintf(stderr, "warning: no tests to run\n");
// imitate Go here (TODO confirm this)
return 0;
}
anyFailed = 0;
for (t = tests; t != NULL; t = t->next) {
printf("=== RUN %s\n", t->name);
if (setjmp(t->returnNowBuf) == 0)
(*(t->f))(t);
runDefers(t);
status = "PASS";
if (t->failed) {
status = "FAIL";
anyFailed = 1;
} else if (t->skipped)
// note that failed overrides skipped
status = "SKIP";
printf("--- %s: %s (%s)\n", status, t->name, "TODO");
}
if (anyFailed) {
printf("FAIL\n");
return 1;
}
printf("PASS\n");
return 0;
}
void testingprivTLogfFull(testingT *t, const char *file, int line, const char *format, ...)
{
va_list ap;
va_start(ap, format);
testingprivTLogvfFull(t, file, line, format, ap);
va_end(ap);
}
void testingprivTLogvfFull(testingT *t, const char *file, int line, const char *format, va_list ap)
{
// TODO extract filename from file
printf("\t%s:%d: ", file, line);
// TODO split into lines separated by \n\t\t and trimming trailing empty lines
vprintf(format, ap);
printf("\n");
}
void testingTFail(testingT *t)
{
t->failed = 1;
}
static void returnNow(testingT *t)
{
// run defers before calling longjmp() just to be safe
runDefers(t);
longjmp(t->returnNowBuf, 1);
}
void testingprivTDoFailNow(testingT *t)
{
testingTFail(t);
returnNow(t);
}
void testingprivTDoSkipNow(testingT *t)
{
t->skipped = 1;
returnNow(t);
}
void testingTDefer(testingT *t, void (*f)(void *data), void *data)
{
struct defer *d;
d = testingprivNew(struct defer);
d->f = f;
d->data = data;
// add to the head of the list so defers are run in reverse order of how they were added
d->next = t->defers;
t->defers = d;
}

View File

@ -0,0 +1,19 @@
Proper vertical text support in uiDrawTextLayout was removed because DirectWrite doesn't add this until Windows 8.1 (unless I drop IDWriteTextLayout and do the script analysis myself; TODO consider this possibility).
On OS X, setting the vertical forms attribute stacks non-vertical scripts in vertical text (rotates each individual glyph) with Core Text, whereas everything else — including Cocoa's text system — rotates entire non-vertical strings. Not sure what to do about this except manually detect which characters to apply the attribute to:
http://www.unicode.org/notes/tn22/RobustVerticalLayout.pdf
http://www.unicode.org/Public/vertical/revision-17/VerticalOrientation-17.txt
In addition, with Core Text, the vertical forms attribute vertically centers the vertical glyphs on the bhorizontal baseline, rather than flush with the text. Using the baseline class attribute doesn't seem to work.
TODO investigate kCJKVerticalRomanPlacementType
If readded, this will need to be a layout-wide setting, not a per-character setting. Pango works right this way; the current Pango code doesn't seem to work.
More links:
https://www.w3.org/TR/2012/NOTE-jlreq-20120403/#line-composition
https://www.w3.org/TR/REC-CSS2/notes.html
TODO indicate where in the attributes.c file that block of code should go (or drop it entirely for the reasons listed above)
TODO same for ui.h
TODO vertical carets

View File

@ -0,0 +1,19 @@
case uiAttributeVerticalForms:
if (spec->Value != 0) {
CFAttributedStringSetAttribute(p->mas, range, kCTVerticalFormsAttributeName, kCFBooleanTrue);
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassRoman);
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicCentered);
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicLow);
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicHigh);
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassHanging);
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassMath);
} else {
CFAttributedStringSetAttribute(p->mas, range, kCTVerticalFormsAttributeName, kCFBooleanFalse);
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassRoman);
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicCentered);
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicLow);
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicHigh);
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassHanging);
// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassMath);
}
break;

View File

@ -0,0 +1,9 @@
PangoGravity gravity;
case uiAttributeVerticalForms:
gravity = PANGO_GRAVITY_SOUTH;
if (spec->Value != 0)
gravity = PANGO_GRAVITY_EAST;
addattr(p, start, end,
pango_attr_gravity_new(gravity));
break;

View File

@ -0,0 +1,15 @@
uint32_t vertval;
case uiAttributeVerticalForms:
// LONGTERM 8 and/or 8.1 add other methods for vertical text
op.p = p;
op.start = start;
op.end = end;
vertval = 0;
if (spec->Value != 0)
vertval = 1;
doOpenType("vert", vertval, &op);
doOpenType("vrt2", vertval, &op);
doOpenType("vkrn", vertval, &op);
doOpenType("vrtr", vertval, &op);
break;

View File

@ -0,0 +1,2 @@
case uiAttributeVerticalForms:
return boolsEqual(attr, spec);

View File

@ -0,0 +1,18 @@
next = "vertical glyph forms";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeVerticalForms;
spec.Value = 1;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " (which you can draw rotated for proper vertical text; for instance, ");
next = "\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86\xE3\x81\x88\xE3\x81\x8A";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeVerticalForms;
spec.Value = 1;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");

View File

@ -0,0 +1,2 @@
// TODO rename to uiAttributeVertical?
uiAttributeVerticalForms, // 0 = off, 1 = on

25
_notes/OS2 Normal file
View File

@ -0,0 +1,25 @@
https://twitter.com/OS2World/status/983822011389620224
Hi. I recommend you today to start with OS/2 Warp 4.52 or ArcaOS ( The tools are almost the same of Warp 3). EDM/2 is a good place to start: http://www.edm2.com
https://twitter.com/OS2World/status/983822594465034240
There is also an RPM with OS/2 Software (https://www.arcanoae.com/resources/downloadables/arca-noae-package-manager/ …), and you can get from it some parts of the OS/2 Toolkit. If you want to develop drivers you require the OS/2 Device Driver Kit. (that is more complex). You can also develop in Qt4 and compile things with gcc.
http://www.edm2.com/index.php/Main_Page
https://www.arcanoae.com/resources/downloadables/arca-noae-package-manager/
http://www.edm2.com/index.php/IBM_OS/2_Toolkit_Documentation
https://www.os2world.com/forum/index.php?topic=953.0
https://www.google.com/search?client=firefox-b-1&ei=QIPPWurPLs7OwAK65K24BA&q=%22OS%2F2+Toolkit%22+site%3Aamazon.com&oq=%22OS%2F2+Toolkit%22+site%3AAmazon.com&gs_l=psy-ab.3...160814.161293.0.161540.2.2.0.0.0.0.128.252.0j2.2.0....0...1c.1.64.psy-ab..0.0.0....0.itT9Og6hC5c
http://www.edm2.com/index.php/List_of_Presentation_Manager_Articles
https://www.ecsoft2.org/
http://www.edm2.com/index.php/Cairo
http://www.edm2.com/index.php/Doodle
http://www.edm2.com/index.php/Workplace_Shell_Toolkit
http://wpstk.netlabs.org/en/site/index.xml
https://en.wikipedia.org/wiki/Workplace_Shell
https://www.google.com/search?q=OS2+alphablending&ie=utf-8&oe=utf-8&client=firefox-b-1
http://www.edm2.com/index.php/List_of_Multimedia_Articles
alphablending:
http://www.osnews.com/story/369/Review-eComStation-OS2-1.0/page3/
http://halfos.ru/documentation/33-os2-api-documentation/67-opengl-os2-developer-reference-guide.html
http://www.altools.com/ALTools/ALSee/ALSee-Image-Viewer.aspx
http://www.os2voice.org/vnewsarc/bn2007122.html
http://www.mozillazine.org/talkback.html?article=194
https://books.google.com/books?id=9cpU5uYCzq4C&pg=PA202&lpg=PA202&dq=%22OS/2%22+alphablending&source=bl&ots=uatEop2jAL&sig=HAa_ofQSKsk6-8tBR6YZ6MRJG_0&hl=en&sa=X&ved=0ahUKEwiDq5HukLbaAhUk8IMKHR7aCw4Q6AEIWTAI#v=onepage&q=%22OS%2F2%22%20alphablending&f=false

1
_notes/caretWidths Normal file
View File

@ -0,0 +1 @@
UWP has this (TODO check its implementation to see if it matches ours) https://docs.microsoft.com/en-us/uwp/api/windows.ui.viewmanagement.uisettings#Windows_UI_ViewManagement_UISettings_CaretWidth

6
_notes/darwinAutoLayout Normal file
View File

@ -0,0 +1,6 @@
https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSLayoutConstraint_Class/
https://developer.apple.com/documentation/uikit/nslayoutconstraint?language=objc
https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html#//apple_ref/doc/uid/TP40010853-CH16-SW1
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html#//apple_ref/doc/uid/TP40010853-CH16-SW1 (Listing 13-3)
https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithScrollViews.html#//apple_ref/doc/uid/TP40010853-CH24-SW1
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithScrollViews.html#//apple_ref/doc/uid/TP40010853-CH24-SW1 ("IMPORTANT Your layout must fully define..." large box)

View File

@ -0,0 +1,8 @@
https://www.google.com/search?q=nsalert+error+icon&client=firefox-b&tbm=isch&source=iu&ictx=1&fir=2iRctS5fJByN0M%253A%252Cw324MTzjHa1bAM%252C_&usg=__x3wpwdNN1L8VI2kHtkKAXFMtpj4%3D&sa=X&ved=0ahUKEwjJzpjN2qDZAhVjw1kKHfOHDoQQ9QEIMTAB#imgrc=2iRctS5fJByN0M:
http://0xced.blogspot.com/2009/11/clalert-nsalert-done-right.html
https://gist.github.com/0xced/228140
http://editra.org/uploads/code/artmac.html
http://mirror.informatimago.com/next/developer.apple.com/documentation/Carbon/Reference/IconServices/index.html
http://www.cocoabuilder.com/archive/cocoa/15427-iconref-to-nsimage.html
https://github.com/lukakerr/Swift-NSUserNotificationPrivate
https://stackoverflow.com/questions/32943220/the-sidebar-icon-image-name-in-osx

197
_notes/misc Normal file
View File

@ -0,0 +1,197 @@
windows data types, "open specifications" on msdn https://msdn.microsoft.com/en-us/library/cc230321.aspx
(I usually use https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx for windows data types)
windows platform update for windows 7
https://msdn.microsoft.com/en-us/library/windows/desktop/jj863687(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/hh802478(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/hh802480(v=vs.85).aspx
DWM, header bars, toolbars
https://stackoverflow.com/questions/41106347/why-is-my-dwmextendframeintoclientaread-window-not-drawing-the-dwm-borders/41125616#41125616
https://developer.gnome.org/hig/stable/header-bars.html.en
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/OSXHIGuidelines/WindowTitleBarToolbar.html#//apple_ref/doc/uid/20000957-CH39-SW1
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/OSXHIGuidelines/ControlsAll.html#//apple_ref/doc/uid/20000957-CH46-SW2
https://msdn.microsoft.com/en-us/library/windows/desktop/bb787329(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/bb787334(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/bb787337(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/cc835034(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/bb787345(v=vs.85).aspx
rendering
https://www.youtube.com/watch?v=UUfXWzp0-DU
text input on windows
https://blogs.msdn.microsoft.com/oldnewthing/20121025-00/?p=6253
https://www.google.com/search?q=translatemessage+site%3Ahttp%3A%2F%2Farchives.miloush.net%2Fmichkap%2Farchive%2F&ie=utf-8&oe=utf-8
http://archives.miloush.net/michkap/archive/2008/04/22/8415843.html
http://archives.miloush.net/michkap/archive/2007/03/25/1948887.html
http://archives.miloush.net/michkap/archive/2006/09/10/748775.html
http://archives.miloush.net/michkap/archive/2004/11/27/270931.html
https://stackoverflow.com/questions/41334851/since-translatemessage-returns-nonzero-unconditionally-how-can-i-tell-either
http://stackoverflow.com/questions/41334851/since-translatemessage-returns-nonzero-unconditionally-how-can-i-tell-either?noredirect=1#comment70107257_41334851
text layouts
https://developer.apple.com/reference/coretext/2110184-ctframe?language=objc
https://msdn.microsoft.com/en-us/library/windows/desktop/dd368203(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/dd368205(v=vs.85).aspx
https://developer.gnome.org/pango/1.30/pango-Layout-Objects.html#pango-layout-set-font-description
https://developer.gnome.org/pango/1.30/pango-Fonts.html#PangoWeight-enum
https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c (code for small caps)
https://bugzilla.gnome.org/show_bug.cgi?id=766148
https://developer.apple.com/documentation/coretext/ctline?preferredLanguage=occ
https://developer.apple.com/reference/coretext/1508876-ctlinegetstringindexforposition?language=objc
https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/CoreText_Programming/Introduction/Introduction.html#//apple_ref/doc/uid/TP40005533-CH1-SW1
https://developer.apple.com/reference/coretext/2110184-ctframe?language=objc
https://developer.apple.com/reference/coretext/2168885-ctline?language=objc
https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html
http://asciiwwdc.com/2013/sessions/205
https://developer.apple.com/reference/coretext/1511332-ctlinegetboundswithoptions?language=objc
https://stackoverflow.com/questions/19709751/ctlinegetboundswithoptions-what-does-the-returned-frame-origin-y-value-mean?rq=1
https://imgur.com/a/lqhzC
https://imgur.com/a/hYeOL
https://www.google.com/search?q=ctline+%22paragraph+spacing%22&ie=utf-8&oe=utf-8
http://stackoverflow.com/questions/8010615/how-do-i-determine-the-of-a-ctline-following-a-ctline
https://imgur.com/a/dlpm2
https://stackoverflow.com/questions/41601845/am-i-missing-something-in-core-text-that-will-let-me-see-which-line-a-certain-po
https://www.google.com/search?q=%22core+text%22+%22combining+character%22&oq=%22core+text%22+%22combining+character%22&gs_l=serp.3...2526898.2528158.0.2528485.21.10.0.0.0.0.216.997.5j3j1.9.0....0...1c.1.64.serp..12.8.904...33i21k1j33i160k1.J69jsB9g0N0
https://github.com/macvim-dev/macvim/issues/400#issuecomment-274292877
https://bugs.webkit.org/show_bug.cgi?id=68287
https://trac.webkit.org/changeset/95391
https://trac.webkit.org/changeset/95391/trunk/Source/WebCore/platform/graphics/mac/ComplexTextControllerCoreText.cpp
http://stackoverflow.com/questions/41857213/is-there-any-way-i-can-get-precise-metrics-line-ascent-line-descent-etc-of
http://pawlan.com/monica/articles/texttutorial/int.html
enter in entry fields, possibly other text layout issues, possibly keyboard shortcuts
https://developer.gnome.org/gtk3/3.10/GtkEntry.html "activate" signal
https://gitlab.gnome.org/GNOME/gtk/blob/gtk-3-10/gtk/gtkentry.c (not sure where)
https://developer.gnome.org/gtk3/3.10/GtkTextView.html signals section of contents
https://gitlab.gnome.org/GNOME/gtk/blob/gtk-3-10/gtk/gtktextview.c (not sure where; closed before I could find out again though gitlab might wreck it anyway)
https://developer.gnome.org/gtk3/3.10/gtk3-Bindings.html
https://gitlab.gnome.org/GNOME/gtk/blob/gtk-3-10/gtk/gtkwidget.c (not sure where)
https://gitlab.gnome.org/GNOME/gtk/blob/gtk-3-10/gtk/gtkbindings.c (not sure where)
https://gitlab.gnome.org/GNOME/gnome-builder/tree/master/libide/keybindings/ide-keybindings.c 404; TODO find the original cgit link in the chat logs to see what hergertme wanted me to see
https://gitlab.gnome.org/GNOME/gnome-builder/tree/master/libide/sourceview/ide-source-view.c likewise
https://gitlab.gnome.org/GNOME/gnome-builder/tree/master/libide/sourceview/ide-source-view-mode.c#n323 likewise
rgb int->float conversion
https://stackoverflow.com/questions/41348339/how-to-convert-rgb-to-hexadecimal-using-gtk?noredirect=1#comment69903262_41348339
windows fonts, hi-dpi
https://stackoverflow.com/questions/41448320/dlgtemplateex-and-ds-shellfont-what-about-point-size
windows default fonts
https://stackoverflow.com/questions/41505151/how-to-draw-text-with-the-default-ui-font-in-directwrite#41505750
uwp stuff
https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/inking-controls
https://msdn.microsoft.com/en-us/windows/uwp/controls-and-patterns/master-details
cmake stuff
https://public.kitware.com/pipermail/cmake/2010-January/034669.html
https://cmake.org/cmake/help/v3.0/command/string.html
https://cmake.org/pipermail/cmake/2003-April/003599.html
opentype features and locales
https://www.microsoft.com/typography/otspec/featurelist.htm
https://en.wikipedia.org/wiki/List_of_typographic_features
https://www.microsoft.com/typography/otspec160/scripttags.htm
https://msdn.microsoft.com/en-us/library/windows/desktop/dd371503(v=vs.85).aspx
gspell (TODO figure out what I wanted from this; possibly spelling checking)
https://git.gnome.org/browse/gspell/tree/gspell/gspell-inline-checker-text-buffer.c
https://git.gnome.org/browse/gtk+/tree/gtk/gtktextview.c?h=gtk-3-10
other assorted notes on text
https://mail.gnome.org/archives/gtk-devel-list/2016-March/msg00037.html
https://mail.gnome.org/archives/gtk-devel-list/2016-June/msg00007.html
https://blogs.msdn.microsoft.com/tsfaware/
https://stackoverflow.com/questions/40453186/what-is-the-correct-modern-way-to-handle-arbitrary-text-input-in-a-custom-contr
https://www.microsoft.com/typography/fonts/family.aspx
windows ribbon
https://twitter.com/_Ninji/status/814918189708668929 (C08rw9TWgAMpLkC.jpg:large)
https://developer.apple.com/library/content/samplecode/CocoaTipsAndTricks/Introduction/Intro.html#//apple_ref/doc/uid/DTS40010039 BlurryView and StaticObjcMethods in particular
printing https://developer.apple.com/library/content/samplecode/PMPrinterPrintWithFile/Introduction/Intro.html#//apple_ref/doc/uid/DTS10003958
auto layout https://developer.apple.com/library/content/releasenotes/UserExperience/RNAutomaticLayout/index.html#//apple_ref/doc/uid/TP40010631
other stuff, mostly custom draw on windows
https://github.com/pauldotknopf/WindowsSDK7-Samples/blob/master/multimedia/DirectWrite/ChooseFont/ChooseFont.cpp at ChooseFontDialog::OnFontSizeNameEdit() definition
https://developer.gnome.org/gtk3/3.10/GtkColorButton.html
https://msdn.microsoft.com/en-us/library/windows/desktop/bb775951(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/bb775951(v=vs.85).aspx between BS_LEFTTEXT and BS_RADIOBUTTON
https://msdn.microsoft.com/en-us/library/windows/desktop/bb775923(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/bb775923(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/bb775802(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/bb775802(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/bb761837(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/bb761837(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/bb761847(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/bb761847(v=vs.85).aspx description + parameters
https://msdn.microsoft.com/en-us/library/windows/desktop/bb775483(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/bb775483(v=vs.85).aspx dwItemSpec parameter
https://msdn.microsoft.com/en-us/library/windows/desktop/bb775951(v=vs.85).aspx#BS_NOTIFY
https://msdn.microsoft.com/en-us/library/windows/desktop/bb775951(v=vs.85).aspx#BS_NOTIFY BS_NOTIFY to BS_RIGHT
http://stackoverflow.com/questions/17678261/how-to-change-color-of-a-button-while-keeping-buttons-functions-in-win32
https://msdn.microsoft.com/en-us/library/windows/desktop/ff919569(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ff919569(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ff919573(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ff919573(v=vs.85).aspx switch (pnm->hdr.code){
http://stackoverflow.com/questions/36756094/will-the-device-context-of-a-child-window-have-the-same-text-metrics-and-text-ex
actually I think most if not all of these were from when I was trying to implement uiColorButton on Windows, so I'm not sure if I still need these, but meh...
more miscellaneous
https://blogs.msdn.microsoft.com/oldnewthing/20160328-00/?p=93204 from "One is virtual memory" to "occured to them that their"
https://blogs.msdn.microsoft.com/oldnewthing/20160331-00/?p=93231
https://msdn.microsoft.com/en-us/library/windows/desktop/aa373631(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ms648395(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ms648395(v=vs.85).aspx
https://blogs.msdn.microsoft.com/oldnewthing/20101118-00/?p=12253#comment-875273 "MTA implies poor service and fare hikes."
from #gtk+
[18:06:48] <zorcon> if i am doing an animation that needs to be updated at every duration(say every 50ms), should i use get_frame_time() from the frameclock to do that? (from add_tick_callback())
[18:12:47] <~Company> zorcon: yes
windows stuff: mostly aero tricks, sdk version macro notes, ribbon stuff, scrollbar stuff too
https://tools.stefankueng.com/SendMessage.html
https://sourceforge.net/p/sendmessage/code/HEAD/tree/trunk/src/SendMessage.vcxproj
https://sourceforge.net/p/sendmessage/code/HEAD/tree/trunk/src/stdafx.h
https://sourceforge.net/p/sendmessage/code/HEAD/tree/trunk/src/MainDlg.h
https://sourceforge.net/u/steveking/profile/
https://github.com/stefankueng/BowPad/tree/master/src + sourceforge original
https://github.com/stefankueng/sktoolslib/blob/master/AeroControls.cpp + sourceforge original
https://tools.stefankueng.com/BowPad.html
https://www.codeproject.com/Articles/1084/Custom-Scrollbar-Library-version-1-1#_articleTop
https://www.google.com/search?client=firefox-b-1&biw=1080&bih=600&ei=gUHRWsrdMYayggf30bfoAw&q=%22aerocontrols.h%22&oq=%22aerocontrols.h%22&gs_l=psy-ab.3..35i39k1l6.1816.3367.0.3413.4.2.0.0.0.0.0.0..1.0....0...1c.1.64.psy-ab..3.1.211.6...211.0Ppw_OREAk0
https://searchcode.com/codesearch/view/7295148/
https://github.com/TortoiseGit/tortoisesvn/blob/master/src/Utils/MiscUI/AeroBaseDlg.h
https://github.com/TortoiseGit/tortoisesvn/blob/master/src/Utils/MiscUI/AeroControls.cpp
https://github.com/gavioto/stexbar/blob/master/Misc/FileTool/src/CleanVerifyDlg.h
windows ribbon colors and dwm customization
https://github.com/stefankueng/BowPad/blob/master/src/MainWindow.cpp
https://msdn.microsoft.com/en-us/library/windows/desktop/dd940487(v=vs.85).aspx
https://github.com/yohei-yoshihara/GameOfLife3D/blob/master/GameOfLife3DLib/gameoflife3d/Ribbon.cpp
https://msdn.microsoft.com/en-us/library/windows/desktop/dd940404(v=vs.85).aspx
https://github.com/stefankueng/sktoolslib/blob/master/AeroColors.cpp
https://gist.github.com/emoacht/bfa852ccc16bdb5465bd
https://stackoverflow.com/questions/4258295/aero-how-to-draw-solid-opaque-colors-on-glass
https://github.com/ComponentFactory/Krypton
https://www.codeproject.com/Articles/620045/Custom-Controls-in-Win-API-Visual-Styles
https://blogs.msdn.microsoft.com/wpf/2010/11/30/systemcolors-reference/
https://stackoverflow.com/questions/25639621/check-when-a-user-changes-windows-glass-brush-theme-color
https://msdn.microsoft.com/en-us/library/windows/desktop/aa969513(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/aa969527(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/dd388198(v=vs.85).aspx
I hope the MFC ribbon can have customizable colors too, otherwise I'll have to do some serious image processing...
windows debugging
https://msdn.microsoft.com/en-us/library/windows/desktop/hh780351(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ff476881(v=vs.85).aspx#Debug
https://msdn.microsoft.com/en-us/library/windows/desktop/hh780343(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/dn457937(v=vs.85).aspx
more OS2 stuff
https://www.google.com/search?q=%22ibm+graphics+development+toolkit%22&ie=utf-8&oe=utf-8&client=firefox-b-1
https://www.google.com/search?q=%22os%2F2+graphics+development+toolkit%22&ie=utf-8&oe=utf-8&client=firefox-b-1
http://www.edm2.com/index.php/IBM_OS/2_Developer%27s_Packages

4
_notes/tableNotes Normal file
View File

@ -0,0 +1,4 @@
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Protocols/NSOutlineViewDataSource_Protocol/index.html#//apple_ref/occ/intfm/NSOutlineViewDataSource/outlineView:child:ofItem:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOutlineViewDelegate_Protocol/index.html#//apple_ref/occ/intfm/NSOutlineViewDelegate/outlineView:didAddRowView:forRow:
https://developer.apple.com/documentation/appkit/nsoutlineviewdelegate/1528320-outlineview?language=objc
https://github.com/mity/mctrl/blob/master/mctrl/treelist.c around treelist_set_subitem()

3
_notes/textSelections Normal file
View File

@ -0,0 +1,3 @@
http://www.catch22.net/tuts/transparent-text
https://msdn.microsoft.com/en-us/library/windows/desktop/ms724371(v=vs.85).aspx
TODO which color did I want from this list

3
_notes/winARM64 Normal file
View File

@ -0,0 +1,3 @@
http://www.os2museum.com/wp/windows-10-arm64-on-qemu/
https://docs.microsoft.com/en-us/windows/uwp/porting/apps-on-arm
http://pete.akeo.ie/2017/05/compiling-desktop-arm-applications-with.html

18
_notes/windowsHighDPI Normal file
View File

@ -0,0 +1,18 @@
https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/mt744321(v=vs.85).aspx
!!!! http://stackoverflow.com/questions/41917279/do-child-windows-have-the-same-dpi-as-their-parents-in-a-per-monitor-aware-appli
https://msdn.microsoft.com/en-us/library/windows/desktop/mt843498(v=vs.85).aspx
https://msdn.microsoft.com/library/windows/desktop/mt791579(v=vs.85).aspx
https://blogs.windows.com/buildingapps/2017/04/04/high-dpi-scaling-improvements-desktop-applications-windows-10-creators-update/
https://channel9.msdn.com/Events/Windows/Windows-Developer-Day-Creators-Update/High-DPI-Improvements-for-Desktop-Developers
https://social.msdn.microsoft.com/Forums/vstudio/en-US/31d2a89f-3518-403e-b1e4-bbe37ac1b0f0/per-monitor-high-dpi-awareness-wmdpichanged?forum=vcgeneral
https://stackoverflow.com/questions/36864894/scaling-the-non-client-area-title-bar-menu-bar-for-per-monitor-high-dpi-suppo/37624363
https://stackoverflow.com/questions/41448320/dlgtemplateex-and-ds-shellfont-what-about-point-size
http://stackoverflow.com/questions/41917279/do-child-windows-have-the-same-dpi-as-their-parents-in-a-per-monitor-aware-appli
https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx
https://msdn.microsoft.com/library/windows/desktop/mt843498(v=vs.85).aspx(d=robot)
https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx#appendix_c_common_high_dpi_issues
https://msdn.microsoft.com/library/windows/desktop/mt843498(v=vs.85).aspx(d=robot)#appendix_c_common_high_dpi_issues
https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx#addressing_high_dpi_issues
https://msdn.microsoft.com/library/windows/desktop/mt843498(v=vs.85).aspx(d=robot)#addressing_high_dpi_issues

7
_notes/windowsPrinting Normal file
View File

@ -0,0 +1,7 @@
https://msdn.microsoft.com/en-us/library/windows/desktop/ff686805(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/dn495653(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/hh448422(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/dd316975(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/dd372919(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ee264335(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/dd145198(v=vs.85).aspx

25
_notes/windowsUWPGlass Normal file
View File

@ -0,0 +1,25 @@
https://twitter.com/omgubuntu/status/962765197109841922
https://github.com/DominicMaas/SoundByte/blob/master/SoundByte.UWP/Resources/Brushes.xaml
https://docs.microsoft.com/en-us/windows/uwp/design/style/acrylic
https://www.google.com/search?q=uwp+acrylic+winapi&ie=utf-8&oe=utf-8&client=firefox-b-1
https://stackoverflow.com/questions/43931709/acrylic-material-in-win32-app
https://stackoverflow.com/questions/44000217/mimicking-acrylic-in-a-win32-app
https://stackoverflow.com/questions/43699256/how-to-use-acrylic-accent-in-windows-10-creators-update
https://stackoverflow.com/questions/39135834/how-to-call-uwp-api-from-converted-win32-app-desktop-app-converter
https://stackoverflow.com/questions/32724187/how-do-you-set-the-glass-blend-colour-on-windows-10
https://channel9.msdn.com/Events/Build/2017/B8034
https://www.reddit.com/r/Windows10/comments/7lzyzc/since_the_taskbar_is_still_win32_and_it_can_have/
https://thenextweb.com/microsoft/2017/05/15/microsoft-fluent-design-system-breaking-windows-10s-new-look/
https://github.com/bbougot/AcrylicWPF
http://vhanla.codigobit.info/2015/07/enable-windows-10-aero-glass-aka-blur.html
http://undoc.airesoft.co.uk/user32.dll/SetWindowCompositionAttribute.php
http://undoc.airesoft.co.uk/user32.dll/GetWindowCompositionAttribute.php
https://msdn.microsoft.com/en-us/library/windows/desktop/aa969530(v=vs.85).aspx
https://github.com/bbougot/AcrylicWPF/blob/master/MainWindow.xaml.cs
https://www.google.com/search?q=%22WCA_ACCENT_POLICY%22&client=firefox-b-1&source=lnt&tbs=cdr%3A1%2Ccd_min%3A1%2F1%2F2001%2Ccd_max%3A12%2F31%2F2013&tbm=
https://github.com/sgothel/jogl/blob/master/src/nativewindow/native/win32/WindowsDWM.c
http://www.brandonfa.lk/win8/win8_devrel_head_x86/uxtheme.h
http://www.brandonfa.lk/win8/
https://github.com/gamozolabs/pdblister
https://github.com/wbenny/pdbex
https://github.com/wbenny/pdbex/releases

View File

@ -0,0 +1,92 @@
// 10 february 2017
#include "../ui.h"
#include "uipriv.h"
// TODO this doesn't handle the case where nLines == 0
// TODO this should never happen even if there are no characters??
// TODO figure out how to make this work on GTK+
void uiDrawCaret(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t pos, int *line)
{
double xoff;
uiDrawTextLayoutLineMetrics m;
struct caretDrawParams cdp;
uiDrawPath *path;
uiDrawBrush brush;
if (*line < 0)
*line = 0;
if (*line > (uiDrawTextLayoutNumLines(layout) - 1))
*line = (uiDrawTextLayoutNumLines(layout) - 1);
// TODO cap pos
xoff = uiDrawTextLayoutByteLocationInLine(layout, pos, *line);
while (xoff < 0) {
size_t start, end;
uiDrawTextLayoutLineByteRange(layout, *line, &start, &end);
if (end < pos) // too far up
(*line)++;
else
(*line)--;
xoff = uiDrawTextLayoutByteLocationInLine(layout, pos, *line);
}
uiDrawTextLayoutLineGetMetrics(layout, *line, &m);
caretDrawParams(c, m.Height, &cdp);
uiDrawSave(c);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathAddRectangle(path,
// TODO add m.X?
x + xoff - cdp.xoff, y + m.Y,
cdp.width, m.Height);
uiDrawPathEnd(path);
brush.Type = uiDrawBrushTypeSolid;
brush.R = cdp.r;
brush.G = cdp.g;
brush.B = cdp.b;
brush.A = cdp.a;
uiDrawFill(c, path, &brush);
uiDrawFreePath(path);
uiDrawRestore(c);
}
void drawTextBackground(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t start, size_t end, uiDrawBrush *brush, int isSelection)
{
int line, nLines;
size_t lstart, lend;
double layoutwid, layoutht;
uiDrawTextLayoutExtents(layout, &layoutwid, &layoutht);
nLines = uiDrawTextLayoutNumLines(layout);
for (line = 0; line < nLines; line++) {
uiDrawTextLayoutLineByteRange(layout, line, &lstart, &lend);
if (start >= lstart && start < lend)
break;
}
while (end - start > 0) {
uiDrawTextLayoutLineMetrics m;
double startx, endx;
uiDrawPath *path;
uiDrawTextLayoutLineByteRange(layout, line, &lstart, &lend);
if (lend > end) // don't cross lines
lend = end;
startx = uiDrawTextLayoutByteLocationInLine(layout, start, line);
// TODO explain this; note the use of start with lend
endx = layoutwid;
if (!isSelection || end <= lend)
endx = uiDrawTextLayoutByteLocationInLine(layout, lend, line);
uiDrawTextLayoutLineGetMetrics(layout, line, &m);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathAddRectangle(path,
x + startx, y + m.Y,
endx - startx, m.Height);
uiDrawPathEnd(path);
uiDrawFill(c, path, brush);
uiDrawFreePath(path);
line++;
start = lend;
}
}

View File

@ -0,0 +1,15 @@
// TODO split these into a separate header file?
// drawtext.c
struct caretDrawParams {
double r;
double g;
double b;
double a;
double xoff;
double width;
};
extern void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p);
extern void drawTextBackground(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t start, size_t end, uiDrawBrush *brush, int isSelection);

View File

@ -0,0 +1,347 @@
// 2 january 2017
#import "uipriv_darwin.h"
#import "draw.h"
@interface lineInfo : NSObject
@property NSRange glyphRange;
@property NSRange characterRange;
@property NSRect lineRect;
@property NSRect lineUsedRect;
@property NSRect glyphBoundingRect;
@property CGFloat baselineOffset;
@property double ascent;
@property double descent;
@property double leading;
@end
@implementation lineInfo
@end
struct uiDrawTextLayout {
// NSTextStorage is subclassed from NSMutableAttributedString
NSTextStorage *attrstr;
NSTextContainer *container;
NSLayoutManager *layoutManager;
// the width as passed into uiDrawTextLayout constructors
double width;
#if 0 /* TODO */
// the *actual* size of the frame
// note: technically, metrics returned from frame are relative to CGPathGetPathBoundingBox(tl->path)
// however, from what I can gather, for a path created by CGPathCreateWithRect(), like we do (with a NULL transform), CGPathGetPathBoundingBox() seems to just return the standardized form of the rect used to create the path
// (this I confirmed through experimentation)
// so we can just use tl->size for adjustments
// we don't need to adjust coordinates by any origin since our rect origin is (0, 0)
CGSize size;
#endif
NSMutableArray<lineInfo *> *lineInfo;
// for converting CFAttributedString indices to byte offsets
size_t *u16tou8;
size_t nu16tou8; // TODO I don't like the casing of this name
};
static NSFont *fontdescToNSFont(uiDrawFontDescriptor *fd)
{
NSFontDescriptor *desc;
NSFont *font;
desc = fontdescToNSFontDescriptor(fd);
font = [NSFont fontWithDescriptor:desc size:fd->Size];
[desc release];
return font;
}
static NSTextStorage *attrstrToTextStorage(uiAttributedString *s, uiDrawFontDescriptor *defaultFont)
{
NSString *nsstr;
NSMutableDictionary *defaultAttrs;
NSTextStorage *attrstr;
nsstr = [[NSString alloc] initWithCharacters:attrstrUTF16(s)
length:attrstrUTF16Len(s)];
defaultAttrs = [NSMutableDictionary new];
[defaultAttrs setObject:fontdescToNSFont(defaultFont)
forKey:NSFontAttributeName];
attrstr = [[NSTextStorage alloc] initWithString:nsstr
attributes:defaultAttrs];
[defaultAttrs release];
[nsstr release];
[attrstr beginEditing];
// TODO copy in the attributes
[attrstr endEditing];
return attrstr;
}
// TODO fine-tune all the properties
uiDrawTextLayout *uiDrawNewTextLayout(uiAttributedString *s, uiDrawFontDescriptor *defaultFont, double width)
{
uiDrawTextLayout *tl;
CGFloat cgwidth;
// TODO correct type?
NSUInteger index;
tl = uiNew(uiDrawTextLayout);
tl->attrstr = attrstrToTextStorage(s, defaultFont);
tl->width = width;
// TODO the documentation on the size property implies this might not be necessary?
cgwidth = (CGFloat) width;
if (cgwidth < 0)
cgwidth = CGFLOAT_MAX;
// TODO rename to tl->textContainer
tl->container = [[NSTextContainer alloc] initWithSize:NSMakeSize(cgwidth, CGFLOAT_MAX)];
// TODO pull the reference for this
[tl->container setLineFragmentPadding:0];
tl->layoutManager = [[NSLayoutManager alloc] init];
[tl->layoutManager setTypesetterBehavior:NSTypesetterLatestBehavior];
[tl->layoutManager addTextContainer:tl->container];
[tl->attrstr addLayoutManager:tl->layoutManager];
// and force a re-layout (TODO get source
[tl->layoutManager glyphRangeForTextContainer:tl->container];
// TODO equivalent of CTFrameProgression for RTL/LTR?
// now collect line information; see https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TextLayout/Tasks/CountLines.html
tl->lineInfo = [NSMutableArray<lineInfo *> new];
index = 0;
while (index < [tl->layoutManager numberOfGlyphs]) {
NSRange glyphRange;
__block lineInfo *li;
li = [lineInfo new];
li.lineRect = [tl->layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&glyphRange];
li.glyphRange = glyphRange;
li.characterRange = [tl->layoutManager characterRangeForGlyphRange:li.glyphRange actualGlyphRange:NULL];
li.lineUsedRect = [tl->layoutManager lineFragmentUsedRectForGlyphAtIndex:index effectiveRange:NULL];
li.glyphBoundingRect = [tl->layoutManager boundingRectForGlyphRange:li.glyphRange inTextContainer:tl->container];
// and this is from http://www.cocoabuilder.com/archive/cocoa/308568-how-to-get-baseline-info.html and http://www.cocoabuilder.com/archive/cocoa/199283-height-and-location-of-text-within-line-in-nslayoutmanager-ignoring-spacing.html
li.baselineOffset = [[tl->layoutManager typesetter] baselineOffsetInLayoutManager:tl->layoutManager glyphIndex:index];
li.ascent = 0;
li.descent = 0;
li.leading = 0;
// imitate what AppKit actually does (or seems to)
[tl->attrstr enumerateAttributesInRange:li.characterRange options:0 usingBlock:^(NSDictionary<NSString *,id> *attrs, NSRange range, BOOL *stop) {
NSFont *f;
NSRect boundingRect;
double v, realAscent, realDescent, realLeading;
BOOL skipAdjust, doAdjust;
f = (NSFont *) [attrs objectForKey:NSFontAttributeName];
if (f == nil) {
f = [NSFont fontWithName:@"Helvetica" size:12.0];
if (f == nil)
f = [NSFont systemFontOfSize:12.0];
}
boundingRect = [f boundingRectForFont];
realAscent = [f ascender];
realDescent = -[f descender];
realLeading = [f leading];
skipAdjust = NO;
doAdjust = NO;
if (NSMaxY(boundingRect) <= realAscent) {
// ascent entirely within bounding box
// don't do anything if there's leading; I'm guessing it's a combination of both of the reasons to skip below... (least sure of this one)
if (realLeading != 0)
skipAdjust = YES;
// does the descent slip outside the bounding box?
if (-realDescent <= NSMinY(boundingRect))
// yes I guess we should assume accents don't collide with the previous line's descent, though I'm not as sure of that as I am about the else clause below...
skipAdjust = YES;
} else {
// ascent outside bounding box ascent does not include accents
// only skip adjustment if there isn't leading (apparently some fonts use the previous line's leading for accents? :/ )
if (realLeading != 0)
skipAdjust = YES;
}
if (!skipAdjust) {
UniChar ch = 0xC0;
CGGlyph glyph;
// there does not seem to be an AppKit API for this...
if (CTFontGetGlyphsForCharacters((CTFontRef) f, &ch, &glyph, 1) != false) {
NSRect bbox;
bbox = [f boundingRectForGlyph:glyph];
if (NSMaxY(bbox) > realAscent)
doAdjust = YES;
if (-realDescent > NSMinY(bbox))
doAdjust = YES;
}
}
// TODO vertical
v = [f ascender];
// TODO get this one back out
if (doAdjust)
v += 0.2 * ([f ascender] + [f descender]);
//v = floor(v + 0.5);
if (li.ascent < v)
li.ascent = v;
v = -[f descender];// floor(-[f descender] + 0.5);
if (li.descent < v)
li.descent = v;
v = [f leading];//floor([f leading] + 0.5);
if (li.leading < v)
li.leading = v;
}];
li.ascent = floor(li.ascent + 0.5);
li.descent = floor(li.descent + 0.5);
li.leading = floor(li.leading + 0.5);
[tl->lineInfo addObject:li];
[li release];
index = glyphRange.location + glyphRange.length;
}
// and finally copy the UTF-16 to UTF-8 index conversion table
tl->u16tou8 = attrstrCopyUTF16ToUTF8(s, &(tl->nu16tou8));
return tl;
}
void uiDrawFreeTextLayout(uiDrawTextLayout *tl)
{
uiFree(tl->u16tou8);
[tl->lineInfo release];
[tl->layoutManager release];
[tl->container release];
[tl->attrstr release];
uiFree(tl);
}
// TODO document that (x,y) is the top-left corner of the *entire frame*
void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
{
NSGraphicsContext *gc;
CGContextFlush(c->c); // just to be safe
[NSGraphicsContext saveGraphicsState];
gc = [NSGraphicsContext graphicsContextWithGraphicsPort:c->c flipped:YES];
[NSGraphicsContext setCurrentContext:gc];
// TODO is this the right point?
// TODO does this draw with the proper default styles?
[tl->layoutManager drawGlyphsForGlyphRange:[tl->layoutManager glyphRangeForTextContainer:tl->container]
atPoint:NSMakePoint(x, y)];
[gc flushGraphics]; // just to be safe
[NSGraphicsContext restoreGraphicsState];
// TODO release gc?
}
// TODO update all of these {
// TODO document that the width and height of a layout is not necessarily the sum of the widths and heights of its constituent lines; this is definitely untrue on OS X, where lines are placed in such a way that the distance between baselines is always integral
// TODO width doesn't include trailing whitespace...
// TODO figure out how paragraph spacing should play into this
// }
void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
{
NSRect r;
// see https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TextLayout/Tasks/StringHeight.html
r = [tl->layoutManager usedRectForTextContainer:tl->container];
*width = r.size.width;
*height = r.size.height;
}
int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)
{
return [tl->lineInfo count];
}
void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)
{
lineInfo *li;
li = (lineInfo *) [tl->lineInfo objectAtIndex:line];
*start = tl->u16tou8[li.characterRange.location];
*end = tl->u16tou8[li.characterRange.location + li.characterRange.length];
}
void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
{
lineInfo *li;
li = (lineInfo *) [tl->lineInfo objectAtIndex:line];
m->X = li.lineRect.origin.x;
m->Y = li.lineRect.origin.y;
// if we use li.lineRect here we get the whole line, not just the part with stuff in it
m->Width = li.lineUsedRect.size.width;
m->Height = li.lineRect.size.height;
// TODO is this correct?
m->BaselineY = (m->Y + m->Height) - li.baselineOffset;
m->Ascent = li.ascent;
m->Descent = li.descent;
m->Leading = li.leading;
// TODO
m->ParagraphSpacingBefore = 0;
m->LineHeightSpace = 0;
m->LineSpacing = 0;
m->ParagraphSpacing = 0;
}
void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, uiDrawTextLayoutHitTestResult *result)
{
#if 0 /* TODO */
CFIndex i;
CTLineRef line;
CFIndex pos;
if (y >= 0) {
for (i = 0; i < tl->nLines; i++) {
double ltop, lbottom;
ltop = tl->lineMetrics[i].Y;
lbottom = ltop + tl->lineMetrics[i].Height;
if (y >= ltop && y < lbottom)
break;
}
result->YPosition = uiDrawTextLayoutHitTestPositionInside;
if (i == tl->nLines) {
i--;
result->YPosition = uiDrawTextLayoutHitTestPositionAfter;
}
} else {
i = 0;
result->YPosition = uiDrawTextLayoutHitTestPositionBefore;
}
result->Line = i;
result->XPosition = uiDrawTextLayoutHitTestPositionInside;
if (x < tl->lineMetrics[i].X) {
result->XPosition = uiDrawTextLayoutHitTestPositionBefore;
// and forcibly return the first character
x = tl->lineMetrics[i].X;
} else if (x > (tl->lineMetrics[i].X + tl->lineMetrics[i].Width)) {
result->XPosition = uiDrawTextLayoutHitTestPositionAfter;
// and forcibly return the last character
x = tl->lineMetrics[i].X + tl->lineMetrics[i].Width;
}
line = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, i);
// TODO copy the part from the docs about this point
pos = CTLineGetStringIndexForPosition(line, CGPointMake(x, 0));
if (pos == kCFNotFound) {
// TODO
}
result->Pos = tl->u16tou8[pos];
#endif
}
void uiDrawTextLayoutByteRangeToRectangle(uiDrawTextLayout *tl, size_t start, size_t end, uiDrawTextLayoutByteRangeRectangle *r)
{
}

View File

@ -0,0 +1,223 @@
// 3 january 2017
#import "uipriv_darwin.h"
// Stupidity: Core Text requires an **exact match for the entire traits dictionary**, otherwise it will **drop ALL the traits**.
// This seems to be true for every function in Core Text that advertises that it performs font matching; I can confirm at the time of writing this is the case for
// - CTFontDescriptorCreateMatchingFontDescriptors() (though the conditions here seem odd)
// - CTFontCreateWithFontDescriptor()
// - CTFontCreateCopyWithAttributes()
// And as a bonus prize, this also applies to Cocoa's NSFontDescriptor methods as well!
// We have to implement the closest match ourselves.
// This needs to be done early in the matching process; in particular, we have to do this before adding any features (such as small caps), because the matching descriptors won't have those.
struct closeness {
NSUInteger index;
double weight;
double italic;
double stretch;
double distance;
};
static double doubleAttr(NSDictionary *traits, NSString *attr)
{
NSNumber *n;
n = (NSNumber *) [traits objectForKey:attr];
return [n doubleValue];
}
struct italicCloseness {
double normal;
double oblique;
double italic;
};
// remember that in closeness, 0 means exact
// in this case, since we define the range, we use 0.5 to mean "close enough" (oblique for italic and italic for oblique) and 1 to mean "not a match"
static const struct italicCloseness italicClosenesses[] = {
[uiDrawTextItalicNormal] = { 0, 1, 1 },
[uiDrawTextItalicOblique] = { 1, 0, 0.5 },
[uiDrawTextItalicItalic] = { 1, 0.5, 0 },
};
// Italics are hard because Core Text does NOT distinguish between italic and oblique.
// All Core Text provides is a slant value and the italic bit of the symbolic traits mask.
// However, Core Text does seem to guarantee (from experimentation; see below) that the slant will be nonzero if and only if the italic bit is set, so we don't need to use the slant value.
// Core Text also seems to guarantee that if a font lists itself as Italic or Oblique by name (font subfamily name, font style name, whatever), it will also have that bit set, so testing this bit does cover all fonts that name themselves as Italic and Oblique. (Again, this is from the below experimentation.)
// TODO there is still one catch that might matter from a user's POV: the reverse is not true the italic bit can be set even if the style of the font face/subfamily/style isn't named as Italic (for example, script typefaces like Adobe's Palace Script MT Std); I don't know what to do about this... I know how to start: find a script font that has an italic form (Adobe's Palace Script MT Std does not; only Regular and Semibold)
// TODO see if the above applies to Cocoa as well
static double italicCloseness(NSFontDescriptor *desc, NSDictionary *traits, uiDrawTextItalic italic)
{
const struct italicCloseness *ic = &(italicClosenesses[italic]);
NSNumber *num;
NSFontSymbolicTraits symbolic;
NSString *styleName;
NSRange range;
BOOL isOblique;
num = (NSNumber *) [traits objectForKey:NSFontSymbolicTrait];
// TODO this should really be a uint32_t-specific one
symbolic = (NSFontSymbolicTraits) [num unsignedIntegerValue];
if ((symbolic & NSFontItalicTrait) == 0)
return ic->normal;
// Okay, now we know it's either Italic or Oblique
// Pango's Core Text code just does a g_strrstr() (backwards case-sensitive search) for "Oblique" in the font's style name (see https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c); let's do that too I guess
// note that NSFontFaceAttribute is the Cocoa equivalent of the style name
isOblique = NO; // default value
styleName = (NSString *) [desc objectForKey:NSFontFaceAttribute];
// TODO is styleName guaranteed?
// TODO NSLiteralSearch?
range = [styleName rangeOfString:@"Oblique" options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound)
return ic->oblique;
return ic->italic;
}
static NSFontDescriptor *matchTraits(NSFontDescriptor *against, double targetWeight, uiDrawTextItalic targetItalic, double targetStretch)
{
NSArray<NSFontDescriptor *> *matching;
NSUInteger i, n;
struct closeness *closeness;
NSFontDescriptor *current;
NSFontDescriptor *out;
matching = [against matchingFontDescriptorsWithMandatoryKeys:nil];
if (matching == nil)
// no matches; give the original back and hope for the best
return against;
n = [matching count];
if (n == 0) {
// likewise
//TODO [matching release];
return against;
}
closeness = (struct closeness *) uiAlloc(n * sizeof (struct closeness), "struct closeness[]");
for (i = 0; i < n; i++) {
NSDictionary *traits;
closeness[i].index = i;
current = (NSFontDescriptor *) [matching objectAtIndex:i];
traits = (NSDictionary *) [current objectForKey:NSFontTraitsAttribute];
if (traits == nil) {
// couldn't get traits; be safe by ranking it lowest
// LONGTERM figure out what the longest possible distances are
closeness[i].weight = 3;
closeness[i].italic = 2;
closeness[i].stretch = 3;
continue;
}
closeness[i].weight = doubleAttr(traits, NSFontWeightTrait) - targetWeight;
closeness[i].italic = italicCloseness(current, traits, targetItalic);
closeness[i].stretch = doubleAttr(traits, NSFontWidthTrait) - targetStretch;
// TODO release traits?
}
// now figure out the 3-space difference between the three and sort by that
// TODO merge this loop with the previous loop?
for (i = 0; i < n; i++) {
double weight, italic, stretch;
weight = closeness[i].weight;
weight *= weight;
italic = closeness[i].italic;
italic *= italic;
stretch = closeness[i].stretch;
stretch *= stretch;
closeness[i].distance = sqrt(weight + italic + stretch);
}
qsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) {
const struct closeness *a = (const struct closeness *) aa;
const struct closeness *b = (const struct closeness *) bb;
// via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions
// LONGTERM is this really the best way? isn't it the same as if (*a < *b) return -1; if (*a > *b) return 1; return 0; ?
return (a->distance > b->distance) - (a->distance < b->distance);
});
// and the first element of the sorted array is what we want
out = (NSFontDescriptor *) [matching objectAtIndex:closeness[0].index];
// TODO is this correct?
[out retain]; // get rule
// release everything
uiFree(closeness);
//TODO [matching release];
// and release the original descriptor since we no longer need it
[against release];
return out;
}
// since uiDrawTextWeight effectively corresponds to OS/2 weights (which roughly correspond to GDI, Pango, and DirectWrite weights, and to a lesser(? TODO) degree, CSS weights), let's just do what Core Text does with OS/2 weights
// TODO this will not be correct for system fonts, which use cached values that have no relation to the OS/2 weights; we need to figure out how to reconcile these
// for more information, see https://bugzilla.gnome.org/show_bug.cgi?id=766148 and TODO_put_blog_post_here_once_I_write_it (TODO keep this line when resolving the above TODO)
static const double weightsToCTWeights[] = {
-1.0, // 0..99
-0.7, // 100..199
-0.5, // 200..299
-0.23, // 300..399
0.0, // 400..499
0.2, // 500..599
0.3, // 600..699
0.4, // 700..799
0.6, // 800..899
0.8, // 900..999
1.0, // 1000
};
static double weightToCTWeight(uiDrawTextWeight weight)
{
int weightClass;
double ctclass;
double rest, weightFloor, nextFloor;
if (weight <= 0)
return -1.0;
if (weight >= 1000)
return 1.0;
weightClass = weight / 100;
rest = (double) weight;
weightFloor = (double) (weightClass * 100);
nextFloor = (double) ((weightClass + 1) * 100);
rest = (rest - weightFloor) / (nextFloor - weightFloor);
ctclass = weightsToCTWeights[weightClass];
return fma(rest,
weightsToCTWeights[weightClass + 1] - ctclass,
ctclass);
}
// based on what Core Text says about actual fonts (system fonts, system fonts in another folder to avoid using cached values, Adobe Font Folio 11, Google Fonts archive, fonts in Windows 7/8.1/10)
static const double stretchesToCTWidths[] = {
[uiDrawTextStretchUltraCondensed] = -0.400000,
[uiDrawTextStretchExtraCondensed] = -0.300000,
[uiDrawTextStretchCondensed] = -0.200000,
[uiDrawTextStretchSemiCondensed] = -0.100000,
[uiDrawTextStretchNormal] = 0.000000,
[uiDrawTextStretchSemiExpanded] = 0.100000,
[uiDrawTextStretchExpanded] = 0.200000,
[uiDrawTextStretchExtraExpanded] = 0.300000,
// this one isn't present in any of the fonts I tested, but it follows naturally from the pattern of the rest, so... (TODO verify by checking the font files directly)
[uiDrawTextStretchUltraExpanded] = 0.400000,
};
NSFontDescriptor *fontdescToNSFontDescriptor(uiDrawFontDescriptor *fd)
{
NSMutableDictionary *attrs;
NSFontDescriptor *basedesc;
attrs = [NSMutableDictionary new];
[attrs setObject:[NSString stringWithUTF8String:fd->Family]
forKey:NSFontFamilyAttribute];
[attrs setObject:[NSNumber numberWithDouble:fd->Size]
forKey:NSFontSizeAttribute];
basedesc = [[NSFontDescriptor alloc] initWithFontAttributes:attrs];
[attrs release];
return matchTraits(basedesc,
weightToCTWeight(fd->Weight),
fd->Italic,
stretchesToCTWidths[fd->Stretch]);
}

View File

@ -0,0 +1,268 @@
// 6 september 2015
#import "uipriv_darwin.h"
// TODO double-check that we are properly handling allocation failures (or just toll free bridge from cocoa)
struct uiDrawFontFamilies {
CFArrayRef fonts;
};
uiDrawFontFamilies *uiDrawListFontFamilies(void)
{
uiDrawFontFamilies *ff;
ff = uiNew(uiDrawFontFamilies);
ff->fonts = CTFontManagerCopyAvailableFontFamilyNames();
if (ff->fonts == NULL)
implbug("error getting available font names (no reason specified) (TODO)");
return ff;
}
int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
{
return CFArrayGetCount(ff->fonts);
}
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)
{
CFStringRef familystr;
char *family;
familystr = (CFStringRef) CFArrayGetValueAtIndex(ff->fonts, n);
// toll-free bridge
family = uiDarwinNSStringToText((NSString *) familystr);
// Get Rule means we do not free familystr
return family;
}
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
{
CFRelease(ff->fonts);
uiFree(ff);
}
struct uiDrawTextFont {
CTFontRef f;
};
uiDrawTextFont *mkTextFont(CTFontRef f, BOOL retain)
{
uiDrawTextFont *font;
font = uiNew(uiDrawTextFont);
font->f = f;
if (retain)
CFRetain(font->f);
return font;
}
uiDrawTextFont *mkTextFontFromNSFont(NSFont *f)
{
// toll-free bridging; we do retain, though
return mkTextFont((CTFontRef) f, YES);
}
static CFMutableDictionaryRef newAttrList(void)
{
CFMutableDictionaryRef attr;
attr = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (attr == NULL)
complain("error creating attribute dictionary in newAttrList()()");
return attr;
}
static void addFontFamilyAttr(CFMutableDictionaryRef attr, const char *family)
{
CFStringRef cfstr;
cfstr = CFStringCreateWithCString(NULL, family, kCFStringEncodingUTF8);
if (cfstr == NULL)
complain("error creating font family name CFStringRef in addFontFamilyAttr()");
CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, cfstr);
CFRelease(cfstr); // dictionary holds its own reference
}
static void addFontSizeAttr(CFMutableDictionaryRef attr, double size)
{
CFNumberRef n;
n = CFNumberCreate(NULL, kCFNumberDoubleType, &size);
CFDictionaryAddValue(attr, kCTFontSizeAttribute, n);
CFRelease(n);
}
#if 0
TODO
// See http://stackoverflow.com/questions/4810409/does-coretext-support-small-caps/4811371#4811371 and https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c for what these do
// And fortunately, unlike the traits (see below), unmatched features are simply ignored without affecting the other features :D
static void addFontSmallCapsAttr(CFMutableDictionaryRef attr)
{
CFMutableArrayRef outerArray;
CFMutableDictionaryRef innerDict;
CFNumberRef numType, numSelector;
int num;
outerArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
if (outerArray == NULL)
complain("error creating outer CFArray for adding small caps attributes in addFontSmallCapsAttr()");
// Apple's headers say these are deprecated, but a few fonts still rely on them
num = kLetterCaseType;
numType = CFNumberCreate(NULL, kCFNumberIntType, &num);
num = kSmallCapsSelector;
numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);
innerDict = newAttrList();
CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);
CFRelease(numType);
CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);
CFRelease(numSelector);
CFArrayAppendValue(outerArray, innerDict);
CFRelease(innerDict); // and likewise for CFArray
// these are the non-deprecated versions of the above; some fonts have these instead
num = kLowerCaseType;
numType = CFNumberCreate(NULL, kCFNumberIntType, &num);
num = kLowerCaseSmallCapsSelector;
numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);
innerDict = newAttrList();
CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);
CFRelease(numType);
CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);
CFRelease(numSelector);
CFArrayAppendValue(outerArray, innerDict);
CFRelease(innerDict); // and likewise for CFArray
CFDictionaryAddValue(attr, kCTFontFeatureSettingsAttribute, outerArray);
CFRelease(outerArray);
}
#endif
#if 0
// Named constants for these were NOT added until 10.11, and even then they were added as external symbols instead of macros, so we can't use them directly :(
// kode54 got these for me before I had access to El Capitan; thanks to him.
#define ourNSFontWeightUltraLight -0.800000
#define ourNSFontWeightThin -0.600000
#define ourNSFontWeightLight -0.400000
#define ourNSFontWeightRegular 0.000000
#define ourNSFontWeightMedium 0.230000
#define ourNSFontWeightSemibold 0.300000
#define ourNSFontWeightBold 0.400000
#define ourNSFontWeightHeavy 0.560000
#define ourNSFontWeightBlack 0.620000
#endif
// Now remember what I said earlier about having to add the small caps traits after calling the above? This gets a dictionary back so we can do so.
CFMutableDictionaryRef extractAttributes(CTFontDescriptorRef desc)
{
CFDictionaryRef dict;
CFMutableDictionaryRef mdict;
dict = CTFontDescriptorCopyAttributes(desc);
// this might not be mutable, so make a mutable copy
mdict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
CFRelease(dict);
return mdict;
}
uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
{
CTFontRef f;
CFMutableDictionaryRef attr;
CTFontDescriptorRef cfdesc;
attr = newAttrList();
addFontFamilyAttr(attr, desc->Family);
addFontSizeAttr(attr, desc->Size);
// now we have to do the traits matching, so create a descriptor, match the traits, and then get the attributes back
cfdesc = CTFontDescriptorCreateWithAttributes(attr);
// TODO release attr?
cfdesc = matchTraits(cfdesc, desc->Weight, desc->Italic, desc->Stretch);
// specify the initial size again just to be safe
f = CTFontCreateWithFontDescriptor(cfdesc, desc->Size, NULL);
// TODO release cfdesc?
return mkTextFont(f, NO); // we hold the initial reference; no need to retain again
}
void uiDrawFreeTextFont(uiDrawTextFont *font)
{
CFRelease(font->f);
uiFree(font);
}
uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
{
return (uintptr_t) (font->f);
}
void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
{
// TODO
}
// text sizes and user space points are identical:
// - https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TypoFeatures/TextSystemFeatures.html#//apple_ref/doc/uid/TP40009459-CH6-51627-BBCCHIFF text points are 72 per inch
// - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Transforms/Transforms.html#//apple_ref/doc/uid/TP40003290-CH204-SW5 user space points are 72 per inch
void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
{
metrics->Ascent = CTFontGetAscent(font->f);
metrics->Descent = CTFontGetDescent(font->f);
metrics->Leading = CTFontGetLeading(font->f);
metrics->UnderlinePos = CTFontGetUnderlinePosition(font->f);
metrics->UnderlineThickness = CTFontGetUnderlineThickness(font->f);
}
// LONGTERM allow line separation and leading to be factored into a wrapping text layout
// TODO reconcile differences in character wrapping on platforms
void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height)
{
struct framesetter fs;
mkFramesetter(layout, &fs);
*width = fs.extents.width;
*height = fs.extents.height;
freeFramesetter(&fs);
}
// LONGTERM provide an equivalent to CTLineGetTypographicBounds() on uiDrawTextLayout?
// LONGTERM keep this for later features and documentation purposes
#if 0
// LONGTERM provide a way to get the image bounds as a separate function later
bounds = CTLineGetImageBounds(line, c);
// though CTLineGetImageBounds() returns CGRectNull on error, it also returns CGRectNull on an empty string, so we can't reasonably check for error
// CGContextSetTextPosition() positions at the baseline in the case of CTLineDraw(); we need the top-left corner instead
CTLineGetTypographicBounds(line, &yoff, NULL, NULL);
// remember that we're flipped, so we subtract
y -= yoff;
CGContextSetTextPosition(c, x, y);
#endif
#if 0
void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)
{
CGColorSpaceRef colorspace;
CGFloat components[4];
CGColorRef color;
// for consistency with windows, use sRGB
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
components[0] = r;
components[1] = g;
components[2] = b;
components[3] = a;
color = CGColorCreate(colorspace, components);
CGColorSpaceRelease(colorspace);
CFAttributedStringSetAttribute(layout->mas,
rangeToCFRange(),
kCTForegroundColorAttributeName,
color);
CGColorRelease(color); // TODO safe?
}
#endif

View File

@ -0,0 +1,314 @@
// 2 january 2017
#import "uipriv_darwin.h"
#import "draw.h"
// TODO on an empty string nLines == 0
// we must prevent this somehow
// TODO in general, every function could be more robust, but we cannot have a situation where there are zero lines
// TODO what happens to extents if only whitespace?
struct uiDrawTextLayout {
CFAttributedStringRef attrstr;
// the width as passed into uiDrawTextLayout constructors
double width;
CTFramesetterRef framesetter;
// the *actual* size of the frame
// note: technically, metrics returned from frame are relative to CGPathGetPathBoundingBox(tl->path)
// however, from what I can gather, for a path created by CGPathCreateWithRect(), like we do (with a NULL transform), CGPathGetPathBoundingBox() seems to just return the standardized form of the rect used to create the path
// (this I confirmed through experimentation)
// so we can just use tl->size for adjustments
// we don't need to adjust coordinates by any origin since our rect origin is (0, 0)
CGSize size;
CGPathRef path;
CTFrameRef frame;
CFArrayRef lines;
CFIndex nLines;
// we compute this once when first creating the layout
uiDrawTextLayoutLineMetrics *lineMetrics;
NSArray *backgroundBlocks;
// for converting CFAttributedString indices from/to byte offsets
size_t *u8tou16;
size_t nUTF8;
size_t *u16tou8;
size_t nUTF16;
};
// TODO document that lines may or may not overlap because ours do in the case of multiple combining characters
static uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size)
{
uiDrawTextLayoutLineMetrics *metrics;
CFArrayRef lines;
CTLineRef line;
CFIndex i, n;
CGFloat ypos;
CGRect bounds, boundsNoLeading;
CGFloat ascent, descent, leading;
CGPoint *origins;
lines = CTFrameGetLines(frame);
n = CFArrayGetCount(lines);
metrics = (uiDrawTextLayoutLineMetrics *) uiAlloc(n * sizeof (uiDrawTextLayoutLineMetrics), "uiDrawTextLayoutLineMetrics[] (text layout)");
origins = (CGPoint *) uiAlloc(n * sizeof (CGPoint), "CGPoint[] (text layout)");
CTFrameGetLineOrigins(frame, CFRangeMake(0, n), origins);
ypos = size.height;
for (i = 0; i < n; i++) {
line = (CTLineRef) CFArrayGetValueAtIndex(lines, i);
bounds = CTLineGetBoundsWithOptions(line, 0);
boundsNoLeading = CTLineGetBoundsWithOptions(line, kCTLineBoundsExcludeTypographicLeading);
// this is equivalent to boundsNoLeading.size.height + boundsNoLeading.origin.y (manually verified)
ascent = bounds.size.height + bounds.origin.y;
descent = -boundsNoLeading.origin.y;
leading = -bounds.origin.y - descent;
// Core Text always rounds these up for paragraph style calculations; there is a flag to control it but it's inaccessible (and this behavior is turned off for old versions of iPhoto)
ascent = floor(ascent + 0.5);
descent = floor(descent + 0.5);
if (leading > 0)
leading = floor(leading + 0.5);
metrics[i].X = origins[i].x;
metrics[i].Y = origins[i].y - descent - leading;
metrics[i].Width = bounds.size.width;
metrics[i].Height = ascent + descent + leading;
metrics[i].BaselineY = origins[i].y;
metrics[i].Ascent = ascent;
metrics[i].Descent = descent;
metrics[i].Leading = leading;
// TODO
metrics[i].ParagraphSpacingBefore = 0;
metrics[i].LineHeightSpace = 0;
metrics[i].LineSpacing = 0;
metrics[i].ParagraphSpacing = 0;
// and finally advance to the next line
ypos += metrics[i].Height;
}
// okay, but now all these metrics are unflipped
// we need to flip them
for (i = 0; i < n; i++) {
metrics[i].Y = size.height - metrics[i].Y;
// go from bottom-left corner to top-left
metrics[i].Y -= metrics[i].Height;
metrics[i].BaselineY = size.height - metrics[i].BaselineY;
}
uiFree(origins);
return metrics;
}
uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)
{
uiDrawTextLayout *tl;
CGFloat cgwidth;
CFRange range, unused;
CGRect rect;
tl = uiNew(uiDrawTextLayout);
tl->attrstr = attrstrToCoreFoundation(p, &(tl->backgroundBlocks));
range.location = 0;
range.length = CFAttributedStringGetLength(tl->attrstr);
tl->width = p->Width;
// TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing
tl->framesetter = CTFramesetterCreateWithAttributedString(tl->attrstr);
if (tl->framesetter == NULL) {
// TODO
}
cgwidth = (CGFloat) (tl->width);
if (cgwidth < 0)
cgwidth = CGFLOAT_MAX;
tl->size = CTFramesetterSuggestFrameSizeWithConstraints(tl->framesetter,
range,
// TODO kCTFramePathWidthAttributeName?
NULL,
CGSizeMake(cgwidth, CGFLOAT_MAX),
&unused); // not documented as accepting NULL (TODO really?)
rect.origin = CGPointZero;
rect.size = tl->size;
tl->path = CGPathCreateWithRect(rect, NULL);
tl->frame = CTFramesetterCreateFrame(tl->framesetter,
range,
tl->path,
// TODO kCTFramePathWidthAttributeName?
NULL);
if (tl->frame == NULL) {
// TODO
}
tl->lines = CTFrameGetLines(tl->frame);
tl->nLines = CFArrayGetCount(tl->lines);
tl->lineMetrics = computeLineMetrics(tl->frame, tl->size);
// and finally copy the UTF-8/UTF-16 conversion tables
tl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8));
tl->u16tou8 = attrstrCopyUTF16ToUTF8(p->String, &(tl->nUTF16));
return tl;
}
void uiDrawFreeTextLayout(uiDrawTextLayout *tl)
{
uiFree(tl->u16tou8);
uiFree(tl->u8tou16);
[tl->backgroundBlocks release];
uiFree(tl->lineMetrics);
CFRelease(tl->frame);
CFRelease(tl->path);
CFRelease(tl->framesetter);
CFRelease(tl->attrstr);
uiFree(tl);
}
// TODO document that (x,y) is the top-left corner of the *entire frame*
void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
{
backgroundBlock b;
CGAffineTransform textMatrix;
CGContextSaveGState(c->c);
// save the text matrix because it's not part of the graphics state
textMatrix = CGContextGetTextMatrix(c->c);
for (b in tl->backgroundBlocks)
b(c, tl, x, y);
// Core Text doesn't draw onto a flipped view correctly; we have to pretend it was unflipped
// see the iOS bits of the first example at https://developer.apple.com/library/mac/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html#//apple_ref/doc/uid/TP40005533-CH12-SW1 (iOS is naturally flipped)
// TODO how is this affected by a non-identity CTM?
CGContextTranslateCTM(c->c, 0, c->height);
CGContextScaleCTM(c->c, 1.0, -1.0);
CGContextSetTextMatrix(c->c, CGAffineTransformIdentity);
// wait, that's not enough; we need to offset y values to account for our new flipping
// TODO explain this calculation
y = c->height - tl->size.height - y;
// CTFrameDraw() draws in the path we specified when creating the frame
// this means that in our usage, CTFrameDraw() will draw at (0,0)
// so move the origin to be at (x,y) instead
// TODO are the signs correct?
CGContextTranslateCTM(c->c, x, y);
CTFrameDraw(tl->frame, c->c);
CGContextSetTextMatrix(c->c, textMatrix);
CGContextRestoreGState(c->c);
}
// TODO document that the width and height of a layout is not necessarily the sum of the widths and heights of its constituent lines
// TODO width doesn't include trailing whitespace...
// TODO figure out how paragraph spacing should play into this
void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
{
*width = tl->size.width;
*height = tl->size.height;
}
int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)
{
return tl->nLines;
}
void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)
{
CTLineRef lr;
CFRange range;
lr = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, line);
range = CTLineGetStringRange(lr);
*start = tl->u16tou8[range.location];
*end = tl->u16tou8[range.location + range.length];
}
void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
{
*m = tl->lineMetrics[line];
}
// in the case of overlapping lines, we read lines first to last and use their bottommost point (Y + Height) to determine where the next line should start for hit-testing
// TODO should we document this?
void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line)
{
int i;
CTLineRef ln;
CFIndex p;
for (i = 0; i < tl->nLines; i++) {
double ltop, lbottom;
ltop = tl->lineMetrics[i].Y;
lbottom = ltop + tl->lineMetrics[i].Height;
// y will already >= ltop at this point since the past lbottom should == (or at least >=, see above) ltop
if (y < lbottom)
break;
}
if (i == tl->nLines)
i--;
*line = i;
ln = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, i);
// note: according to the docs, we pass a y of 0 for this since the is the baseline of that line (the point is relative to the line)
// note: x is relative to the line origin
x -= tl->lineMetrics[*line].X;
p = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0));
if (p == kCFNotFound) {
// TODO
}
*pos = tl->u16tou8[p];
}
double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)
{
CTLineRef lr;
CFRange range;
pos = tl->u8tou16[pos];
if (line < 0 || line >= tl->nLines)
return -1;
lr = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, line);
range = CTLineGetStringRange(lr);
// note: >, not >=, because the position at end is valid!
if (pos < range.location || pos > (range.location + range.length))
return -1;
// no point in checking the return; we already validated everything and 0 is a valid return for the first index :/
// note: the result is relative to the line origin (TODO find documentation to support this)
// TODO document that these functions do this
return CTLineGetOffsetForStringIndex(lr, pos, NULL) + tl->lineMetrics[line].X;
}
void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p)
{
NSColor *cc;
CGFloat cr, cg, cb, ca;
// Interface Builder sets this as the insertion point color for a NSTextView by default
cc = [NSColor controlTextColor];
// the given color may not be an RGBA color, which will cause the -getRed:green:blue:alpha: call to throw an exception
cc = [cc colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
[cc getRed:&cr green:&cg blue:&cb alpha:&ca];
p->r = cr;
p->g = cg;
p->b = cb;
p->a = ca;
// both cc and the controlTextColor it was made from will be autoreleased since they aren't new or init calls
// TODO disabled carets have some blending applied...
// okay there doesn't seem to be any defined metrics for these, argh...
p->width = 1;
p->xoff = 0;
}

View File

@ -0,0 +1,60 @@
darwin
int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)
{
return CFArrayGetCount([tl->forLines lines]);
}
void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)
{
CTLineRef lr;
CFRange range;
lr = (CTLineRef) CFArrayGetValueAtIndex([tl->forLines lines], line);
range = CTLineGetStringRange(lr);
*start = tl->u16tou8[range.location];
if (tl->empty)
*end = *start;
else
*end = tl->u16tou8[range.location + range.length];
}
unix
int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)
{
return pango_layout_get_line_count(tl->layout);
}
void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)
{
PangoLayoutLine *pll;
pll = pango_layout_get_line_readonly(tl->layout, line);
*start = pll->start_index;
*end = pll->start_index + pll->length;
// TODO unref pll?
}
windows
int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)
{
return 0;
#if 0
TODO
return tl->nLines;
#endif
}
// DirectWrite doesn't provide a direct way to do this, so we have to do this manually
// TODO does that comment still apply here or to the code at the top of this file?
void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)
{
#if 0
TODO
*start = tl->lineInfo[line].startPos;
*start = tl->u16tou8[*start];
*end = tl->lineInfo[line].endPos - tl->lineInfo[line].newlineCount;
*end = tl->u16tou8[*end];
#endif
}

View File

@ -0,0 +1,72 @@
typedef struct uiDrawTextLayoutLineMetrics uiDrawTextLayoutLineMetrics;
// Height will equal ParagraphSpacingBefore + LineHeightSpace + Ascent + Descent + Leading + LineSpacing + ParagraphSpacing.
// The above values are listed in vertical order, from top to bottom.
// Ascent + Descent + Leading will give you the typographic bounds
// of the text. BaselineY is the boundary between Ascent and Descent.
// X, Y, and BaselineY are all in the layout's coordinate system, so the
// start point of the baseline will be at (X, BaselineY). All values are
// nonnegative.
struct uiDrawTextLayoutLineMetrics {
// This describes the overall bounding box of the line.
double X;
double Y;
double Width;
double Height;
// This describes the typographic bounds of the line.
double BaselineY;
double Ascent;
double Descent;
double Leading;
// This describes any additional whitespace.
// TODO come up with better names for these.
double ParagraphSpacingBefore;
double LineHeightSpace;
double LineSpacing;
double ParagraphSpacing;
// TODO trailing whitespace?
};
// uiDrawTextLayoutNumLines() returns the number of lines in tl.
// This number will always be greater than or equal to 1; a text
// layout with no text only has one line.
_UI_EXTERN int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl);
// uiDrawTextLayoutLineByteRange() returns the byte indices of the
// text that falls into the given line of tl as [start, end).
_UI_EXTERN void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end);
_UI_EXTERN void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m);
// TODO rewrite this documentation
// uiDrawTextLayoutHitTest() returns the byte offset and line closest
// to the given point. The point is relative to the top-left of the layout.
// If the point is outside the layout itself, the closest point is chosen;
// this allows the function to be used for cursor positioning with the
// mouse. Do keep the returned line in mind if used in this way; the
// user might click on the end of a line, at which point the cursor
// might be at the trailing edge of the last grapheme on the line
// (subject to the operating system's APIs).
_UI_EXTERN void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line);
// uiDrawTextLayoutByteLocationInLine() returns the point offset
// into the given line that the given byte position stands. This is
// relative to the line's X position (as returned by
// uiDrawTextLayoutLineGetMetrics()), which in turn is relative to
// the top-left of the layout. This function can be used for cursor
// positioning: if start and end are the start and end of the line
// (as returned by uiDrawTextLayoutLineByteRange()), you will get
// the correct offset, even if pos is at the end of the line. If pos is not
// in the range [start, end], a negative value will be returned,
// indicating you need to move the cursor to another line.
// TODO make sure this works right for right-aligned and center-aligned lines and justified lines and RTL text
_UI_EXTERN double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line);
_UI_EXTERN void uiDrawCaret(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t pos, int *line);
// TODO allow blinking
// TODO allow secondary carets

View File

@ -0,0 +1,171 @@
diff --git a/darwin/attrstr.m b/darwin/attrstr.m
index fd45ec25..86039fad 100644
--- a/darwin/attrstr.m
+++ b/darwin/attrstr.m
@@ -403,8 +403,13 @@ static CTParagraphStyleRef mkParagraphStyle(uiDrawTextLayoutParams *p)
return ps;
}
-CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray **backgroundBlocks)
+static const UniChar emptyChars[] = { 0x20, 0x0 };
+static const CFIndex emptyCharCount = 1;
+
+CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, BOOL *isEmpty, NSArray **backgroundBlocks)
{
+ const UniChar *chars;
+ CFIndex charCount;
CFStringRef cfstr;
CFMutableDictionaryRef defaultAttrs;
CTFontRef defaultCTFont;
@@ -413,7 +418,15 @@ CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray
CFMutableAttributedStringRef mas;
struct foreachParams fep;
- cfstr = CFStringCreateWithCharacters(NULL, attrstrUTF16(p->String), attrstrUTF16Len(p->String));
+ *isEmpty = NO;
+ chars = attrstrUTF16(p->String);
+ charCount = attrstrUTF16Len(p->String);
+ if (charCount == 0) {
+ *isEmpty = YES;
+ chars = emptyChars;
+ charCount = emptyCharCount;
+ }
+ cfstr = CFStringCreateWithCharacters(NULL, chars, charCount);
if (cfstr == NULL) {
// TODO
}
diff --git a/darwin/drawtext.m b/darwin/drawtext.m
index 1fa5920e..65912383 100644
--- a/darwin/drawtext.m
+++ b/darwin/drawtext.m
@@ -2,13 +2,16 @@
#import "uipriv_darwin.h"
#import "draw.h"
-// TODO on an empty string nLines == 0
-// we must prevent this somehow
-// TODO in general, every function could be more robust, but we cannot have a situation where there are zero lines
+// TODO in general, every function could be more robust
// TODO what happens to extents if only whitespace?
+// TODO for empty layouts:
+// - check if alignment correct compared to other OSs, or expected behavior at all
+// - double-check if uiAttributedString allows zero-length attributes; I forget if I did
struct uiDrawTextLayout {
CFAttributedStringRef attrstr;
+ // this is needed because Core Text will give us an empty line array on a frame made with an empty string
+ BOOL isEmpty;
// the width as passed into uiDrawTextLayout constructors
double width;
@@ -41,7 +44,7 @@
};
// TODO document that lines may or may not overlap because ours do in the case of multiple combining characters
-static uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size)
+static uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size, BOOL isEmpty)
{
uiDrawTextLayoutLineMetrics *metrics;
CFArrayRef lines;
@@ -79,6 +82,8 @@
metrics[i].X = origins[i].x;
metrics[i].Y = origins[i].y - descent - leading;
metrics[i].Width = bounds.size.width;
+ if (isEmpty)
+ metrics[i].Width = 0;
metrics[i].Height = ascent + descent + leading;
metrics[i].BaselineY = origins[i].y;
@@ -117,7 +122,7 @@
CGRect rect;
tl = uiNew(uiDrawTextLayout);
- tl->attrstr = attrstrToCoreFoundation(p, &(tl->backgroundBlocks));
+ tl->attrstr = attrstrToCoreFoundation(p, &(tl->isEmpty), &(tl->backgroundBlocks));
range.location = 0;
range.length = CFAttributedStringGetLength(tl->attrstr);
tl->width = p->Width;
@@ -152,7 +157,7 @@
tl->lines = CTFrameGetLines(tl->frame);
tl->nLines = CFArrayGetCount(tl->lines);
- tl->lineMetrics = computeLineMetrics(tl->frame, tl->size);
+ tl->lineMetrics = computeLineMetrics(tl->frame, tl->size, tl->isEmpty);
// and finally copy the UTF-8/UTF-16 conversion tables
tl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8));
@@ -180,6 +185,9 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
backgroundBlock b;
CGAffineTransform textMatrix;
+ if (tl->isEmpty)
+ return;
+
CGContextSaveGState(c->c);
// save the text matrix because it's not part of the graphics state
textMatrix = CGContextGetTextMatrix(c->c);
@@ -216,6 +224,8 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
{
*width = tl->size.width;
+ if (tl->isEmpty)
+ *width = 0;
*height = tl->size.height;
}
@@ -233,6 +243,8 @@ void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start
range = CTLineGetStringRange(lr);
*start = tl->u16tou8[range.location];
*end = tl->u16tou8[range.location + range.length];
+ if (tl->isEmpty)
+ *start = *end;
}
void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
@@ -262,14 +274,17 @@ void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *p
*line = i;
ln = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, i);
- // note: according to the docs, we pass a y of 0 for this since the is the baseline of that line (the point is relative to the line)
- // note: x is relative to the line origin
x -= tl->lineMetrics[*line].X;
- p = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0));
- if (p == kCFNotFound) {
- // TODO
+ *pos = 0;
+ if (!tl->isEmpty) {
+ // note: according to the docs, we pass a y of 0 for this since the is the baseline of that line (the point is relative to the line)
+ // note: x is relative to the line origin
+ p = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0));
+ if (p == kCFNotFound) {
+ // TODO
+ }
+ *pos = tl->u16tou8[p];
}
- *pos = tl->u16tou8[p];
}
double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)
@@ -282,6 +297,9 @@ void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *p
return -1;
lr = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, line);
range = CTLineGetStringRange(lr);
+ // TODO is the behavior of this part correct?
+ if (tl->isEmpty)
+ range.length = 0;
// note: >, not >=, because the position at end is valid!
if (pos < range.location || pos > (range.location + range.length))
return -1;
diff --git a/darwin/uipriv_darwin.h b/darwin/uipriv_darwin.h
index 0303c32c..d44ee410 100644
--- a/darwin/uipriv_darwin.h
+++ b/darwin/uipriv_darwin.h
@@ -151,7 +151,7 @@ extern void fontdescFromCTFontDescriptor(CTFontDescriptorRef ctdesc, uiDrawFontD
extern void initUnderlineColors(void);
extern void uninitUnderlineColors(void);
typedef void (^backgroundBlock)(uiDrawContext *c, uiDrawTextLayout *layout, double x, double y);
-extern CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray **backgroundBlocks);
+extern CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, BOOL *isEmpty, NSArray **backgroundBlocks);
// aat.m
typedef void (^aatBlock)(uint16_t type, uint16_t selector);

View File

@ -0,0 +1,217 @@
// 6 september 2015
#include "uipriv_unix.h"
#include "draw.h"
struct uiDrawFontFamilies {
PangoFontFamily **f;
int n;
};
uiDrawFontFamilies *uiDrawListFontFamilies(void)
{
uiDrawFontFamilies *ff;
PangoFontMap *map;
ff = uiNew(uiDrawFontFamilies);
map = pango_cairo_font_map_get_default();
pango_font_map_list_families(map, &(ff->f), &(ff->n));
// do not free map; it's a shared resource
return ff;
}
int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
{
return ff->n;
}
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)
{
PangoFontFamily *f;
f = ff->f[n];
return uiUnixStrdupText(pango_font_family_get_name(f));
}
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
{
g_free(ff->f);
uiFree(ff);
}
struct uiDrawTextFont {
PangoFont *f;
};
uiDrawTextFont *mkTextFont(PangoFont *f, gboolean ref)
{
uiDrawTextFont *font;
font = uiNew(uiDrawTextFont);
font->f = f;
if (ref)
g_object_ref(font->f);
return font;
}
PangoFont *pangoDescToPangoFont(PangoFontDescription *pdesc)
{
PangoFont *f;
PangoContext *context;
// in this case, the context is necessary for the metrics to be correct
context = mkGenericPangoCairoContext();
f = pango_font_map_load_font(pango_cairo_font_map_get_default(), context, pdesc);
if (f == NULL) {
// LONGTERM
g_error("[libui] no match in pangoDescToPangoFont(); report to andlabs");
}
g_object_unref(context);
return f;
}
uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
{
PangoFont *f;
PangoFontDescription *pdesc;
pdesc = pango_font_description_new();
pango_font_description_set_family(pdesc,
desc->Family);
pango_font_description_set_size(pdesc,
(gint) (desc->Size * PANGO_SCALE));
pango_font_description_set_weight(pdesc,
pangoWeights[desc->Weight]);
pango_font_description_set_style(pdesc,
pangoItalics[desc->Italic]);
pango_font_description_set_stretch(pdesc,
pangoStretches[desc->Stretch]);
f = pangoDescToPangoFont(pdesc);
pango_font_description_free(pdesc);
return mkTextFont(f, FALSE); // we hold the initial reference; no need to ref
}
void uiDrawFreeTextFont(uiDrawTextFont *font)
{
g_object_unref(font->f);
uiFree(font);
}
uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
{
return (uintptr_t) (font->f);
}
void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
{
PangoFontDescription *pdesc;
// this creates a copy; we free it later
pdesc = pango_font_describe(font->f);
// TODO
pango_font_description_free(pdesc);
}
// See https://developer.gnome.org/pango/1.30/pango-Cairo-Rendering.html#pango-Cairo-Rendering.description
// Note that we convert to double before dividing to make sure the floating-point stuff is right
#define pangoToCairo(pango) (((double) (pango)) / PANGO_SCALE)
#define cairoToPango(cairo) ((gint) ((cairo) * PANGO_SCALE))
void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
{
PangoFontMetrics *pm;
pm = pango_font_get_metrics(font->f, NULL);
metrics->Ascent = pangoToCairo(pango_font_metrics_get_ascent(pm));
metrics->Descent = pangoToCairo(pango_font_metrics_get_descent(pm));
// Pango doesn't seem to expose this :( Use 0 and hope for the best.
metrics->Leading = 0;
metrics->UnderlinePos = pangoToCairo(pango_font_metrics_get_underline_position(pm));
metrics->UnderlineThickness = pangoToCairo(pango_font_metrics_get_underline_thickness(pm));
pango_font_metrics_unref(pm);
}
// note: PangoCairoLayouts are tied to a given cairo_t, so we can't store one in this device-independent structure
struct uiDrawTextLayout {
char *s;
ptrdiff_t *graphemes;
PangoFont *defaultFont;
double width;
PangoAttrList *attrs;
};
uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width)
{
uiDrawTextLayout *layout;
PangoContext *context;
layout = uiNew(uiDrawTextLayout);
layout->s = g_strdup(text);
context = mkGenericPangoCairoContext();
layout->graphemes = graphemes(layout->s, context);
g_object_unref(context);
layout->defaultFont = defaultFont->f;
g_object_ref(layout->defaultFont); // retain a copy
uiDrawTextLayoutSetWidth(layout, width);
layout->attrs = pango_attr_list_new();
return layout;
}
void uiDrawFreeTextLayout(uiDrawTextLayout *layout)
{
pango_attr_list_unref(layout->attrs);
g_object_unref(layout->defaultFont);
uiFree(layout->graphemes);
g_free(layout->s);
uiFree(layout);
}
void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width)
{
layout->width = width;
}
static void prepareLayout(uiDrawTextLayout *layout, PangoLayout *pl)
{
// again, this makes a copy
desc = pango_font_describe(layout->defaultFont);
pango_layout_set_attributes(pl, layout->attrs);
}
void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
{
PangoLayout *pl;
pl = pango_cairo_create_layout(c->cr);
}
static void addAttr(uiDrawTextLayout *layout, PangoAttribute *attr, int startChar, int endChar)
{
attr->start_index = layout->graphemes[startChar];
attr->end_index = layout->graphemes[endChar];
pango_attr_list_insert(layout->attrs, attr);
// pango_attr_list_insert() takes attr; we don't free it
}
void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)
{
PangoAttribute *attr;
guint16 rr, gg, bb, aa;
rr = (guint16) (r * 65535);
gg = (guint16) (g * 65535);
bb = (guint16) (b * 65535);
aa = (guint16) (a * 65535);
attr = pango_attr_foreground_new(rr, gg, bb);
addAttr(layout, attr, startChar, endChar);
// TODO what if aa == 0?
attr = FUTURE_pango_attr_foreground_alpha_new(aa);
if (attr != NULL)
addAttr(layout, attr, startChar, endChar);
}

View File

@ -0,0 +1,227 @@
// 17 january 2017
#include "uipriv_unix.h"
#include "draw.h"
// TODO
// - if the RTL override is at the beginning of a line, the preceding space is included?
// - nLines == 0: mostly works, except the width is wrong if the paragraph alignment is center or right...
// - TODO check whitespace and line bounds
struct uiDrawTextLayout {
PangoLayout *layout;
GPtrArray *backgroundClosures;
uiDrawTextLayoutLineMetrics *lineMetrics;
// TODO change everything to use this
int nLines;
};
// TODO neither these nor the overall extents seem to include trailing whitespace... we need to figure that out too
static void computeLineMetrics(uiDrawTextLayout *tl)
{
PangoLayoutIter *iter;
PangoLayoutLine *pll;
PangoRectangle lineStartPos, lineExtents;
int i, n;
uiDrawTextLayoutLineMetrics *m;
n = tl->nLines; // TODO remove this variable
tl->lineMetrics = (uiDrawTextLayoutLineMetrics *) uiAlloc(n * sizeof (uiDrawTextLayoutLineMetrics), "uiDrawTextLayoutLineMetrics[] (text layout)");
iter = pango_layout_get_iter(tl->layout);
m = tl->lineMetrics;
for (i = 0; i < n; i++) {
int baselineY;
// TODO we use this instead of _get_yrange() because of the block of text in that function's description about how line spacing is distributed in Pango; we have to worry about this when we start adding line spacing...
baselineY = pango_layout_iter_get_baseline(iter);
pll = pango_layout_iter_get_line_readonly(iter);
pango_layout_index_to_pos(tl->layout, pll->start_index, &lineStartPos);
pango_layout_line_get_extents(pll, NULL, &lineExtents);
// TODO unref pll?
// TODO is this correct for RTL glyphs?
m->X = pangoToCairo(lineStartPos.x);
// TODO fix the whole combined not being updated shenanigans in the static build (here because ugh)
m->Y = pangoToCairo(baselineY - PANGO_ASCENT(lineExtents));
// TODO this does not include the last space if any
m->Width = pangoToCairo(lineExtents.width);
m->Height = pangoToCairo(lineExtents.height);
m->BaselineY = pangoToCairo(baselineY);
m->Ascent = pangoToCairo(PANGO_ASCENT(lineExtents));
m->Descent = pangoToCairo(PANGO_DESCENT(lineExtents));
m->Leading = 0; // TODO
m->ParagraphSpacingBefore = 0; // TODO
m->LineHeightSpace = 0; // TODO
m->LineSpacing = 0; // TODO
m->ParagraphSpacing = 0; // TODO
// don't worry about the return value; we're not using this after the last line
pango_layout_iter_next_line(iter);
m++;
}
pango_layout_iter_free(iter);
}
uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)
{
uiDrawTextLayout *tl;
PangoContext *context;
PangoFontDescription *desc;
PangoAttrList *attrs;
int pangoWidth;
tl = uiNew(uiDrawTextLayout);
// in this case, the context is necessary to create the layout
// the layout takes a ref on the context so we can unref it afterward
context = mkGenericPangoCairoContext();
tl->layout = pango_layout_new(context);
g_object_unref(context);
// this is safe; pango_layout_set_text() copies the string
pango_layout_set_text(tl->layout, uiAttributedStringString(p->String), -1);
desc = pango_font_description_new();
pango_font_description_set_family(desc, p->DefaultFont->Family);
pango_font_description_set_style(desc, pangoItalics[p->DefaultFont->Italic]);
// for the most part, pango weights correlate to ours
// the differences:
// - Book — libui: 350, Pango: 380
// - Ultra Heavy — libui: 950, Pango: 1000
// TODO figure out what to do about this misalignment
pango_font_description_set_weight(desc, p->DefaultFont->Weight);
pango_font_description_set_stretch(desc, pangoStretches[p->DefaultFont->Stretch]);
// see https://developer.gnome.org/pango/1.30/pango-Fonts.html#pango-font-description-set-size and https://developer.gnome.org/pango/1.30/pango-Glyph-Storage.html#pango-units-from-double
pango_font_description_set_size(desc, pango_units_from_double(p->DefaultFont->Size));
pango_layout_set_font_description(tl->layout, desc);
// this is safe; the description is copied
pango_font_description_free(desc);
pangoWidth = cairoToPango(p->Width);
if (p->Width < 0)
pangoWidth = -1;
pango_layout_set_width(tl->layout, pangoWidth);
pango_layout_set_alignment(tl->layout, pangoAligns[p->Align]);
attrs = attrstrToPangoAttrList(p, &(tl->backgroundClosures));
pango_layout_set_attributes(tl->layout, attrs);
pango_attr_list_unref(attrs);
tl->nLines = pango_layout_get_line_count(tl->layout);
computeLineMetrics(tl);
return tl;
}
void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
{
*m = tl->lineMetrics[line];
}
// TODO
#if 0
{
PangoLayoutLine *pll;
pll = pango_layout_get_line_readonly(tl->layout, line);
// TODO unref?
}
#endif
// note: Pango will not let us place the cursor at the end of a line the same way other OSs do; see https://git.gnome.org/browse/pango/tree/pango/pango-layout.c?id=f4cbd27f4e5bf8490ea411190d41813e14f12165#n4204
// ideally there'd be a way to say "I don't need this hack; I'm well behaved" but GTK+ 2 and 3 AND Qt 4 and 5 all behave like this, with the behavior seeming to date back to TkTextView, so...
void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line)
{
int p, trailing;
int i;
// this is layout-global, so it takes line origins into account
pango_layout_xy_to_index(tl->layout,
cairoToPango(x), cairoToPango(y),
&p, &trailing);
// on a trailing hit, align to the nearest cluster
// fortunately Pango provides that info directly
if (trailing != 0)
p += trailing;
*pos = p;
for (i = 0; i < tl->nLines; i++) {
double ltop, lbottom;
ltop = tl->lineMetrics[i].Y;
lbottom = ltop + tl->lineMetrics[i].Height;
// y will already >= ltop at this point since the past lbottom should == ltop
if (y < lbottom)
break;
}
if (i == pango_layout_get_line_count(tl->layout))
i--;
*line = i;
}
double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)
{
PangoLayoutLine *pll;
gboolean trailing;
int pangox;
if (line < 0 || line >= tl->nLines)
return -1;
pll = pango_layout_get_line_readonly(tl->layout, line);
// note: >, not >=, because the position at end is valid!
if (pos < pll->start_index || pos > (pll->start_index + pll->length))
return -1;
// this behavior seems correct
// there's also PadWrite's TextEditor::GetCaretRect() but that requires state...
// TODO where does this fail?
// TODO optimize everything to avoid calling strlen()
trailing = 0;
if (pos != 0 && pos != strlen(pango_layout_get_text(tl->layout)) && pos == (pll->start_index + pll->length)) {
pos--;
trailing = 1;
}
pango_layout_line_index_to_x(pll, pos, trailing, &pangox);
// TODO unref pll?
// this is relative to the beginning of the line
return pangoToCairo(pangox) + tl->lineMetrics[line].X;
}
// note: we can't use gtk_render_insertion_cursor() because that doesn't take information about what line to render on
// we'll just recreate what it does
void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p)
{
GdkColor *color;
GdkRGBA rgba;
gfloat aspectRatio;
gint width, xoff;
gtk_style_context_get_style(c->style,
"cursor-color", &color,
"cursor-aspect-ratio", &aspectRatio,
NULL);
if (color != NULL) {
p->r = ((double) (color->red)) / 65535.0;
p->g = ((double) (color->green)) / 65535.0;
p->b = ((double) (color->blue)) / 65535.0;
p->a = 1.0;
gdk_color_free(color);
} else {
gtk_style_context_get_color(c->style, GTK_STATE_FLAG_NORMAL, &rgba);
p->r = rgba.red;
p->g = rgba.green;
p->b = rgba.blue;
p->a = rgba.alpha;
}
// GTK+ itself uses integer arithmetic here; let's do the same
width = height * aspectRatio + 1;
// TODO this is for LTR
xoff = width / 2;
p->xoff = xoff;
p->width = width;
}

View File

@ -0,0 +1,311 @@
// 22 december 2015
#include "uipriv_windows.hpp"
#include "draw.hpp"
// TODO really migrate
// notes:
// only available in windows 8 and newer:
// - character spacing
// - kerning control
// - justficiation (how could I possibly be making this up?!)
// - vertical text (SERIOUSLY?! WHAT THE ACTUAL FUCK, MICROSOFT?!?!?!? DID YOU NOT THINK ABOUT THIS THE FIRST TIME, TRYING TO IMPROVE THE INTERNATIONALIZATION OF WINDOWS 7?!?!?! bonus: some parts of MSDN even say 8.1 and up only!)
struct uiDrawFontFamilies {
fontCollection *fc;
};
uiDrawFontFamilies *uiDrawListFontFamilies(void)
{
struct uiDrawFontFamilies *ff;
ff = uiNew(struct uiDrawFontFamilies);
ff->fc = loadFontCollection();
return ff;
}
int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
{
return ff->fc->fonts->GetFontFamilyCount();
}
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)
{
IDWriteFontFamily *family;
WCHAR *wname;
char *name;
HRESULT hr;
hr = ff->fc->fonts->GetFontFamily(n, &family);
if (hr != S_OK)
logHRESULT(L"error getting font out of collection", hr);
wname = fontCollectionFamilyName(ff->fc, family);
name = toUTF8(wname);
uiFree(wname);
family->Release();
return name;
}
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
{
fontCollectionFree(ff->fc);
uiFree(ff);
}
struct uiDrawTextFont {
IDWriteFont *f;
WCHAR *family; // save for convenience in uiDrawNewTextLayout()
double size;
};
uiDrawTextFont *mkTextFont(IDWriteFont *df, BOOL addRef, WCHAR *family, BOOL copyFamily, double size)
{
uiDrawTextFont *font;
WCHAR *copy;
HRESULT hr;
font = uiNew(uiDrawTextFont);
font->f = df;
if (addRef)
font->f->AddRef();
if (copyFamily) {
copy = (WCHAR *) uiAlloc((wcslen(family) + 1) * sizeof (WCHAR), "WCHAR[]");
wcscpy(copy, family);
font->family = copy;
} else
font->family = family;
font->size = size;
return font;
}
// TODO MinGW-w64 is missing this one
#define DWRITE_FONT_WEIGHT_SEMI_LIGHT (DWRITE_FONT_WEIGHT(350))
uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
{
uiDrawTextFont *font;
IDWriteFontCollection *collection;
UINT32 index;
BOOL exists;
struct dwriteAttr attr;
IDWriteFontFamily *family;
WCHAR *wfamily;
IDWriteFont *match;
HRESULT hr;
// always get the latest available font information
hr = dwfactory->GetSystemFontCollection(&collection, TRUE);
if (hr != S_OK)
logHRESULT(L"error getting system font collection", hr);
wfamily = toUTF16(desc->Family);
hr = collection->FindFamilyName(wfamily, &index, &exists);
if (hr != S_OK)
logHRESULT(L"error finding font family", hr);
if (!exists)
implbug("LONGTERM family not found in uiDrawLoadClosestFont()", hr);
hr = collection->GetFontFamily(index, &family);
if (hr != S_OK)
logHRESULT(L"error loading font family", hr);
attr.weight = desc->Weight;
attr.italic = desc->Italic;
attr.stretch = desc->Stretch;
attrToDWriteAttr(&attr);
hr = family->GetFirstMatchingFont(
attr.dweight,
attr.dstretch,
attr.ditalic,
&match);
if (hr != S_OK)
logHRESULT(L"error loading font", hr);
font = mkTextFont(match,
FALSE, // we own the initial reference; no need to add another one
wfamily, FALSE, // will be freed with font
desc->Size);
family->Release();
collection->Release();
return font;
}
void uiDrawFreeTextFont(uiDrawTextFont *font)
{
font->f->Release();
uiFree(font->family);
uiFree(font);
}
uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
{
return (uintptr_t) (font->f);
}
void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
{
// TODO
desc->Size = font->size;
// TODO
}
// text sizes are 1/72 of an inch
// points in Direct2D are 1/96 of an inch (https://msdn.microsoft.com/en-us/library/windows/desktop/ff684173%28v=vs.85%29.aspx, https://msdn.microsoft.com/en-us/library/windows/desktop/hh447022%28v=vs.85%29.aspx)
// As for the actual conversion from design units, see:
// - http://cboard.cprogramming.com/windows-programming/136733-directwrite-font-height-issues.html
// - https://sourceforge.net/p/vstgui/mailman/message/32483143/
// - http://xboxforums.create.msdn.com/forums/t/109445.aspx
// - https://msdn.microsoft.com/en-us/library/dd183564%28v=vs.85%29.aspx
// - http://www.fontbureau.com/blog/the-em/
// TODO make points here about how DIPs in DirectWrite == DIPs in Direct2D; if not, figure out what they really are? for the width and layout functions later
static double scaleUnits(double what, double designUnitsPerEm, double size)
{
return (what / designUnitsPerEm) * (size * (96.0 / 72.0));
}
void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
{
DWRITE_FONT_METRICS dm;
font->f->GetMetrics(&dm);
metrics->Ascent = scaleUnits(dm.ascent, dm.designUnitsPerEm, font->size);
metrics->Descent = scaleUnits(dm.descent, dm.designUnitsPerEm, font->size);
// TODO what happens if dm.xxx is negative?
// TODO remember what this was for
metrics->Leading = scaleUnits(dm.lineGap, dm.designUnitsPerEm, font->size);
metrics->UnderlinePos = scaleUnits(dm.underlinePosition, dm.designUnitsPerEm, font->size);
metrics->UnderlineThickness = scaleUnits(dm.underlineThickness, dm.designUnitsPerEm, font->size);
}
// some attributes, such as foreground color, can't be applied until after we establish a Direct2D context :/ so we have to prepare all attributes in advance
// also since there's no way to clear the attributes from a layout en masse (apart from overwriting them all), we'll play it safe by creating a new layout each time
enum layoutAttrType {
layoutAttrColor,
};
struct layoutAttr {
enum layoutAttrType type;
int start;
int end;
double components[4];
};
struct uiDrawTextLayout {
WCHAR *text;
size_t textlen;
size_t *graphemes;
double width;
IDWriteTextFormat *format;
std::vector<struct layoutAttr> *attrs;
};
uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width)
{
uiDrawTextLayout *layout;
HRESULT hr;
layout = uiNew(uiDrawTextLayout);
hr = dwfactory->CreateTextFormat(defaultFont->family,
NULL,
defaultFont->f->GetWeight(),
defaultFont->f->GetStyle(),
defaultFont->f->GetStretch(),
if (hr != S_OK)
logHRESULT(L"error creating IDWriteTextFormat", hr);
layout->text = toUTF16(text);
layout->textlen = wcslen(layout->text);
layout->graphemes = graphemes(layout->text);
uiDrawTextLayoutSetWidth(layout, width);
layout->attrs = new std::vector<struct layoutAttr>;
return layout;
}
void uiDrawFreeTextLayout(uiDrawTextLayout *layout)
{
delete layout->attrs;
layout->format->Release();
uiFree(layout->graphemes);
uiFree(layout->text);
uiFree(layout);
}
IDWriteTextLayout *prepareLayout(uiDrawTextLayout *layout, ID2D1RenderTarget *rt)
{
IDWriteTextLayout *dl;
DWRITE_TEXT_RANGE range;
IUnknown *unkBrush;
HRESULT hr;
for (const struct layoutAttr &attr : *(layout->attrs)) {
range.startPosition = layout->graphemes[attr.start];
range.length = layout->graphemes[attr.end] - layout->graphemes[attr.start];
switch (attr.type) {
case layoutAttrColor:
if (rt == NULL) // determining extents, not drawing
break;
unkBrush = mkSolidBrush(rt,
attr.components[0],
attr.components[1],
attr.components[2],
attr.components[3]);
hr = dl->SetDrawingEffect(unkBrush, range);
unkBrush->Release(); // associated with dl
break;
default:
hr = E_FAIL;
logHRESULT(L"invalid text attribute type", hr);
}
if (hr != S_OK)
logHRESULT(L"error adding attribute to text layout", hr);
}
return dl;
}
void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
{
IDWriteTextLayout *dl;
D2D1_POINT_2F pt;
ID2D1Brush *black;
HRESULT hr;
// TODO document that fully opaque black is the default text color; figure out whether this is upheld in various scenarios on other platforms
black = mkSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0);
dl = prepareLayout(layout, c->rt);
pt.x = x;
pt.y = y;
// TODO D2D1_DRAW_TEXT_OPTIONS_NO_SNAP?
// TODO D2D1_DRAW_TEXT_OPTIONS_CLIP?
// TODO when setting 8.1 as minimum, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT?
c->rt->DrawTextLayout(pt, dl, black, D2D1_DRAW_TEXT_OPTIONS_NONE);
dl->Release();
black->Release();
}
void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)
{
struct layoutAttr attr;
attr.type = layoutAttrColor;
attr.start = startChar;
attr.end = endChar;
attr.components[0] = r;
attr.components[1] = g;
attr.components[2] = b;
attr.components[3] = a;
layout->attrs->push_back(attr);
}

View File

@ -0,0 +1,667 @@
// 17 january 2017
#include "uipriv_windows.hpp"
#include "draw.hpp"
// TODO
// - consider the warnings about antialiasing in the PadWrite sample
// - if that's not a problem, do we have overlapping rects in the hittest sample? I can't tell...
// - empty string: nLines == 1 and all checks out except extents has x == 0 when not left aligned
// - paragraph alignment is subject to RTL mirroring; see if it is on other platforms
// - add overhang info to metrics?
// TODO verify our renderer is correct, especially with regards to snapping
struct uiDrawTextLayout {
IDWriteTextFormat *format;
IDWriteTextLayout *layout;
std::vector<backgroundFunc> *backgroundFuncs;
UINT32 nLines;
struct lineInfo *lineInfo;
// for converting DirectWrite indices from/to byte offsets
size_t *u8tou16;
size_t nUTF8;
size_t *u16tou8;
size_t nUTF16;
};
// TODO copy notes about DirectWrite DIPs being equal to Direct2D DIPs here
// typographic points are 1/72 inch; this parameter is 1/96 inch
// fortunately Microsoft does this too, in https://msdn.microsoft.com/en-us/library/windows/desktop/dd371554%28v=vs.85%29.aspx
#define pointSizeToDWriteSize(size) (size * (96.0 / 72.0))
struct lineInfo {
size_t startPos; // in UTF-16 points
size_t endPos;
size_t newlineCount;
double x;
double y;
double width;
double height;
double baseline;
};
// this function is deeply indebted to the PadWrite sample: https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/multimedia/DirectWrite/PadWrite/TextEditor.cpp
static void computeLineInfo(uiDrawTextLayout *tl)
{
DWRITE_LINE_METRICS *dlm;
size_t nextStart;
UINT32 i, j;
DWRITE_HIT_TEST_METRICS *htm;
UINT32 nFragments, unused;
HRESULT hr;
// TODO make sure this is legal; if not, switch to GetMetrics() and use its line count field instead
hr = tl->layout->GetLineMetrics(NULL, 0, &(tl->nLines));
// ugh, HRESULT_TO_WIN32() is an inline function and is not constexpr so we can't use switch here
if (hr == S_OK) {
// TODO what do we do here
} else if (hr != E_NOT_SUFFICIENT_BUFFER)
logHRESULT(L"error getting number of lines in IDWriteTextLayout", hr);
tl->lineInfo = (struct lineInfo *) uiAlloc(tl->nLines * sizeof (struct lineInfo), "struct lineInfo[] (text layout)");
dlm = new DWRITE_LINE_METRICS[tl->nLines];
// we can't pass NULL here; it outright crashes if we do
// TODO verify the numbers haven't changed
hr = tl->layout->GetLineMetrics(dlm, tl->nLines, &unused);
if (hr != S_OK)
logHRESULT(L"error getting IDWriteTextLayout line metrics", hr);
// assume the first line starts at position 0 and the string flow is incremental
nextStart = 0;
for (i = 0; i < tl->nLines; i++) {
tl->lineInfo[i].startPos = nextStart;
tl->lineInfo[i].endPos = nextStart + dlm[i].length;
tl->lineInfo[i].newlineCount = dlm[i].newlineLength;
nextStart = tl->lineInfo[i].endPos;
// a line can have multiple fragments; for example, if there's a bidirectional override in the middle of a line
hr = tl->layout->HitTestTextRange(tl->lineInfo[i].startPos, (tl->lineInfo[i].endPos - tl->lineInfo[i].newlineCount) - tl->lineInfo[i].startPos,
0, 0,
NULL, 0, &nFragments);
if (hr != S_OK && hr != E_NOT_SUFFICIENT_BUFFER)
logHRESULT(L"error getting IDWriteTextLayout line fragment count", hr);
htm = new DWRITE_HIT_TEST_METRICS[nFragments];
// TODO verify unused == nFragments?
hr = tl->layout->HitTestTextRange(tl->lineInfo[i].startPos, (tl->lineInfo[i].endPos - tl->lineInfo[i].newlineCount) - tl->lineInfo[i].startPos,
0, 0,
htm, nFragments, &unused);
// TODO can this return E_NOT_SUFFICIENT_BUFFER again?
if (hr != S_OK)
logHRESULT(L"error getting IDWriteTextLayout line fragment metrics", hr);
// TODO verify htm.textPosition and htm.length against dtm[i]/tl->lineInfo[i]?
tl->lineInfo[i].x = htm[0].left;
tl->lineInfo[i].y = htm[0].top;
// TODO does this not include trailing whitespace? I forget
tl->lineInfo[i].width = htm[0].width;
tl->lineInfo[i].height = htm[0].height;
for (j = 1; j < nFragments; j++) {
// this is correct even if the leftmost fragment on the line is RTL
if (tl->lineInfo[i].x > htm[j].left)
tl->lineInfo[i].x = htm[j].left;
tl->lineInfo[i].width += htm[j].width;
// TODO verify y and height haven't changed?
}
// TODO verify dlm[i].height == htm.height?
delete[] htm;
// TODO on Windows 8.1 and/or 10 we can use DWRITE_LINE_METRICS1 to get specific info about the ascent and descent; do we have an alternative?
// TODO and even on those platforms can we somehow split tyographic leading from spacing?
// TODO and on that note, can we have both line spacing proportionally above and uniformly below?
tl->lineInfo[i].baseline = dlm[i].baseline;
}
delete[] dlm;
}
// TODO should be const but then I can't operator[] on it; the real solution is to find a way to do designated array initializers in C++11 but I do not know enough C++ voodoo to make it work (it is possible but no one else has actually done it before)
static std::map<uiDrawTextAlign, DWRITE_TEXT_ALIGNMENT> dwriteAligns = {
{ uiDrawTextAlignLeft, DWRITE_TEXT_ALIGNMENT_LEADING },
{ uiDrawTextAlignCenter, DWRITE_TEXT_ALIGNMENT_CENTER },
{ uiDrawTextAlignRight, DWRITE_TEXT_ALIGNMENT_TRAILING },
};
uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)
{
uiDrawTextLayout *tl;
WCHAR *wDefaultFamily;
DWRITE_WORD_WRAPPING wrap;
FLOAT maxWidth;
HRESULT hr;
tl = uiNew(uiDrawTextLayout);
wDefaultFamily = toUTF16(p->DefaultFont->Family);
hr = dwfactory->CreateTextFormat(
wDefaultFamily, NULL,
uiprivWeightToDWriteWeight(p->DefaultFont->Weight),
uiprivItalicToDWriteStyle(p->DefaultFont->Italic),
uiprivStretchToDWriteStretch(p->DefaultFont->Stretch),
pointSizeToDWriteSize(p->DefaultFont->Size),
// see http://stackoverflow.com/questions/28397971/idwritefactorycreatetextformat-failing and https://msdn.microsoft.com/en-us/library/windows/desktop/dd368203.aspx
// TODO use the current locale?
L"",
&(tl->format));
if (hr != S_OK)
logHRESULT(L"error creating IDWriteTextFormat", hr);
hr = tl->format->SetTextAlignment(dwriteAligns[p->Align]);
if (hr != S_OK)
logHRESULT(L"error applying text layout alignment", hr);
hr = dwfactory->CreateTextLayout(
(const WCHAR *) attrstrUTF16(p->String), attrstrUTF16Len(p->String),
tl->format,
// FLOAT is float, not double, so this should work... TODO
FLT_MAX, FLT_MAX,
&(tl->layout));
if (hr != S_OK)
logHRESULT(L"error creating IDWriteTextLayout", hr);
// and set the width
// this is the only wrapping mode (apart from "no wrap") available prior to Windows 8.1 (TODO verify this fact) (TODO this should be the default anyway)
wrap = DWRITE_WORD_WRAPPING_WRAP;
maxWidth = (FLOAT) (p->Width);
if (p->Width < 0) {
// TODO is this wrapping juggling even necessary?
wrap = DWRITE_WORD_WRAPPING_NO_WRAP;
// setting the max width in this case technically isn't needed since the wrap mode will simply ignore the max width, but let's do it just to be safe
maxWidth = FLT_MAX; // see TODO above
}
hr = tl->layout->SetWordWrapping(wrap);
if (hr != S_OK)
logHRESULT(L"error setting IDWriteTextLayout word wrapping mode", hr);
hr = tl->layout->SetMaxWidth(maxWidth);
if (hr != S_OK)
logHRESULT(L"error setting IDWriteTextLayout max layout width", hr);
attrstrToIDWriteTextLayoutAttrs(p, tl->layout, &(tl->backgroundFuncs));
computeLineInfo(tl);
// and finally copy the UTF-8/UTF-16 index conversion tables
tl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8));
tl->u16tou8 = attrstrCopyUTF16ToUTF8(p->String, &(tl->nUTF16));
// TODO can/should this be moved elsewhere?
uiFree(wDefaultFamily);
return tl;
}
void uiDrawFreeTextLayout(uiDrawTextLayout *tl)
{
uiFree(tl->u16tou8);
uiFree(tl->u8tou16);
uiFree(tl->lineInfo);
delete tl->backgroundFuncs;
tl->layout->Release();
tl->format->Release();
uiFree(tl);
}
static HRESULT mkSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a, ID2D1SolidColorBrush **brush)
{
D2D1_BRUSH_PROPERTIES props;
D2D1_COLOR_F color;
ZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES));
props.opacity = 1.0;
// identity matrix
props.transform._11 = 1;
props.transform._22 = 1;
color.r = r;
color.g = g;
color.b = b;
color.a = a;
return rt->CreateSolidColorBrush(
&color,
&props,
brush);
}
static ID2D1SolidColorBrush *mustMakeSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a)
{
ID2D1SolidColorBrush *brush;
HRESULT hr;
hr = mkSolidBrush(rt, r, g, b, a, &brush);
if (hr != S_OK)
logHRESULT(L"error creating solid brush", hr);
return brush;
}
// some of the stuff we want to do isn't possible with what DirectWrite provides itself; we need to do it ourselves
// this is based on http://www.charlespetzold.com/blog/2014/01/Character-Formatting-Extensions-with-DirectWrite.html
class textRenderer : public IDWriteTextRenderer {
ULONG refcount;
ID2D1RenderTarget *rt;
BOOL snap;
ID2D1SolidColorBrush *black;
public:
textRenderer(ID2D1RenderTarget *rt, BOOL snap, ID2D1SolidColorBrush *black)
{
this->refcount = 1;
this->rt = rt;
this->snap = snap;
this->black = black;
}
// IUnknown
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
{
if (ppvObject == NULL)
return E_POINTER;
if (riid == IID_IUnknown ||
riid == __uuidof (IDWritePixelSnapping) ||
riid == __uuidof (IDWriteTextRenderer)) {
this->AddRef();
*ppvObject = this;
return S_OK;
}
*ppvObject = NULL;
return E_NOINTERFACE;
}
virtual ULONG STDMETHODCALLTYPE AddRef(void)
{
this->refcount++;
return this->refcount;
}
virtual ULONG STDMETHODCALLTYPE Release(void)
{
this->refcount--;
if (this->refcount == 0) {
delete this;
return 0;
}
return this->refcount;
}
// IDWritePixelSnapping
virtual HRESULT STDMETHODCALLTYPE GetCurrentTransform(void *clientDrawingContext, DWRITE_MATRIX *transform)
{
D2D1_MATRIX_3X2_F d2dtf;
if (transform == NULL)
return E_POINTER;
this->rt->GetTransform(&d2dtf);
transform->m11 = d2dtf._11;
transform->m12 = d2dtf._12;
transform->m21 = d2dtf._21;
transform->m22 = d2dtf._22;
transform->dx = d2dtf._31;
transform->dy = d2dtf._32;
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE GetPixelsPerDip(void *clientDrawingContext, FLOAT *pixelsPerDip)
{
FLOAT dpix, dpiy;
if (pixelsPerDip == NULL)
return E_POINTER;
this->rt->GetDpi(&dpix, &dpiy);
*pixelsPerDip = dpix / 96;
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled(void *clientDrawingContext, BOOL *isDisabled)
{
if (isDisabled == NULL)
return E_POINTER;
*isDisabled = !this->snap;
return S_OK;
}
// IDWriteTextRenderer
virtual HRESULT STDMETHODCALLTYPE DrawGlyphRun(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE measuringMode, const DWRITE_GLYPH_RUN *glyphRun, const DWRITE_GLYPH_RUN_DESCRIPTION *glyphRunDescription, IUnknown *clientDrawingEffect)
{
D2D1_POINT_2F baseline;
textDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect;
ID2D1SolidColorBrush *brush;
baseline.x = baselineOriginX;
baseline.y = baselineOriginY;
brush = this->black;
if (t != NULL && t->hasColor) {
HRESULT hr;
hr = mkSolidBrush(this->rt, t->r, t->g, t->b, t->a, &brush);
if (hr != S_OK)
return hr;
}
this->rt->DrawGlyphRun(
baseline,
glyphRun,
brush,
measuringMode);
if (t != NULL && t->hasColor)
brush->Release();
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE DrawInlineObject(void *clientDrawingContext, FLOAT originX, FLOAT originY, IDWriteInlineObject *inlineObject, BOOL isSideways, BOOL isRightToLeft, IUnknown *clientDrawingEffect)
{
if (inlineObject == NULL)
return E_POINTER;
return inlineObject->Draw(clientDrawingContext, this,
originX, originY,
isSideways, isRightToLeft,
clientDrawingEffect);
}
virtual HRESULT STDMETHODCALLTYPE DrawStrikethrough(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_STRIKETHROUGH *strikethrough, IUnknown *clientDrawingEffect)
{
// we don't support strikethrough
return E_UNEXPECTED;
}
virtual HRESULT STDMETHODCALLTYPE DrawUnderline(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_UNDERLINE *underline, IUnknown *clientDrawingEffect)
{
textDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect;
ID2D1SolidColorBrush *brush;
D2D1_RECT_F rect;
D2D1::Matrix3x2F pixeltf;
FLOAT dpix, dpiy;
D2D1_POINT_2F pt;
if (underline == NULL)
return E_POINTER;
if (t == NULL) // we can only get here through an underline
return E_UNEXPECTED;
brush = this->black;
if (t->hasUnderlineColor) {
HRESULT hr;
hr = mkSolidBrush(this->rt, t->ur, t->ug, t->ub, t->ua, &brush);
if (hr != S_OK)
return hr;
} else if (t->hasColor) {
// TODO formalize this rule
HRESULT hr;
hr = mkSolidBrush(this->rt, t->r, t->g, t->b, t->a, &brush);
if (hr != S_OK)
return hr;
}
rect.left = baselineOriginX;
rect.top = baselineOriginY + underline->offset;
rect.right = rect.left + underline->width;
rect.bottom = rect.top + underline->thickness;
switch (t->u) {
case uiDrawUnderlineStyleSingle:
this->rt->FillRectangle(&rect, brush);
break;
case uiDrawUnderlineStyleDouble:
// TODO do any of the matrix methods return errors?
// TODO standardize double-underline shape across platforms? wavy underline shape?
this->rt->GetTransform(&pixeltf);
this->rt->GetDpi(&dpix, &dpiy);
pixeltf = pixeltf * D2D1::Matrix3x2F::Scale(dpix / 96, dpiy / 96);
pt.x = 0;
pt.y = rect.top;
pt = pixeltf.TransformPoint(pt);
rect.top = (FLOAT) ((int) (pt.y + 0.5));
pixeltf.Invert();
pt = pixeltf.TransformPoint(pt);
rect.top = pt.y;
// first line
rect.top -= underline->thickness;
// and it seems we need to recompute this
rect.bottom = rect.top + underline->thickness;
this->rt->FillRectangle(&rect, brush);
// second line
rect.top += 2 * underline->thickness;
rect.bottom = rect.top + underline->thickness;
this->rt->FillRectangle(&rect, brush);
break;
case uiDrawUnderlineStyleSuggestion:
{ // TODO get rid of the extra block
// TODO properly clean resources on failure
// TODO use fully qualified C overloads for all methods
// TODO ensure all methods properly have errors handled
ID2D1PathGeometry *path;
ID2D1GeometrySink *sink;
double amplitude, period, xOffset, yOffset;
double t;
bool first = true;
HRESULT hr;
hr = d2dfactory->CreatePathGeometry(&path);
if (hr != S_OK)
return hr;
hr = path->Open(&sink);
if (hr != S_OK)
return hr;
amplitude = underline->thickness;
period = 5 * underline->thickness;
xOffset = baselineOriginX;
yOffset = baselineOriginY + underline->offset;
for (t = 0; t < underline->width; t++) {
double x, angle, y;
D2D1_POINT_2F pt;
x = t + xOffset;
angle = 2 * uiPi * fmod(x, period) / period;
y = amplitude * sin(angle) + yOffset;
pt.x = x;
pt.y = y;
if (first) {
sink->BeginFigure(pt, D2D1_FIGURE_BEGIN_HOLLOW);
first = false;
} else
sink->AddLine(pt);
}
sink->EndFigure(D2D1_FIGURE_END_OPEN);
hr = sink->Close();
if (hr != S_OK)
return hr;
sink->Release();
this->rt->DrawGeometry(path, brush, underline->thickness);
path->Release();
}
break;
}
if (t->hasUnderlineColor || t->hasColor)
brush->Release();
return S_OK;
}
};
// TODO this ignores clipping?
void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
{
D2D1_POINT_2F pt;
ID2D1SolidColorBrush *black;
textRenderer *renderer;
HRESULT hr;
for (const auto &f : *(tl->backgroundFuncs))
f(c, tl, x, y);
// TODO document that fully opaque black is the default text color; figure out whether this is upheld in various scenarios on other platforms
// TODO figure out if this needs to be cleaned out
black = mustMakeSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0);
#define renderD2D 0
#define renderOur 1
#if renderD2D
pt.x = x;
pt.y = y;
// TODO D2D1_DRAW_TEXT_OPTIONS_NO_SNAP?
// TODO D2D1_DRAW_TEXT_OPTIONS_CLIP?
// TODO LONGTERM when setting 8.1 as minimum (TODO verify), D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT?
// TODO what is our pixel snapping setting related to the OPTIONS enum values?
c->rt->DrawTextLayout(pt, tl->layout, black, D2D1_DRAW_TEXT_OPTIONS_NONE);
#endif
#if renderD2D && renderOur
// draw ours semitransparent so we can check
// TODO get the actual color Charles Petzold uses and use that
black->Release();
black = mustMakeSolidBrush(c->rt, 1.0, 0.0, 0.0, 0.75);
#endif
#if renderOur
renderer = new textRenderer(c->rt,
TRUE, // TODO FALSE for no-snap?
black);
hr = tl->layout->Draw(NULL,
renderer,
x, y);
if (hr != S_OK)
logHRESULT(L"error drawing IDWriteTextLayout", hr);
renderer->Release();
#endif
black->Release();
}
// TODO for a single line the height includes the leading; should it? TextEdit on OS X always includes the leading and/or paragraph spacing, otherwise Klee won't work...
// TODO width does not include trailing whitespace
void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
{
DWRITE_TEXT_METRICS metrics;
HRESULT hr;
hr = tl->layout->GetMetrics(&metrics);
if (hr != S_OK)
logHRESULT(L"error getting IDWriteTextLayout layout metrics", hr);
*width = metrics.width;
// TODO make sure the behavior of this on empty strings is the same on all platforms (ideally should be 0-width, line height-height; TODO note this in the docs too)
*height = metrics.height;
}
int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)
{
return tl->nLines;
}
// DirectWrite doesn't provide a direct way to do this, so we have to do this manually
// TODO does that comment still apply here or to the code at the top of this file?
void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)
{
*start = tl->lineInfo[line].startPos;
*start = tl->u16tou8[*start];
*end = tl->lineInfo[line].endPos - tl->lineInfo[line].newlineCount;
*end = tl->u16tou8[*end];
}
void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)
{
m->X = tl->lineInfo[line].x;
m->Y = tl->lineInfo[line].y;
m->Width = tl->lineInfo[line].width;
m->Height = tl->lineInfo[line].height;
// TODO rename tl->lineInfo[line].baseline to .baselineOffset or something of the sort to make its meaning more clear
m->BaselineY = tl->lineInfo[line].y + tl->lineInfo[line].baseline;
m->Ascent = tl->lineInfo[line].baseline;
m->Descent = tl->lineInfo[line].height - tl->lineInfo[line].baseline;
m->Leading = 0; // TODO
m->ParagraphSpacingBefore = 0; // TODO
m->LineHeightSpace = 0; // TODO
m->LineSpacing = 0; // TODO
m->ParagraphSpacing = 0; // TODO
}
// this algorithm comes from Microsoft's PadWrite sample, following TextEditor::SetSelectionFromPoint()
// TODO go back through all of these and make sure we convert coordinates properly
// TODO same for OS X
void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line)
{
DWRITE_HIT_TEST_METRICS m;
BOOL trailing, inside;
size_t p;
UINT32 i;
HRESULT hr;
hr = tl->layout->HitTestPoint(x, y,
&trailing, &inside,
&m);
if (hr != S_OK)
logHRESULT(L"error hit-testing IDWriteTextLayout", hr);
p = m.textPosition;
// on a trailing hit, align to the nearest cluster
if (trailing) {
DWRITE_HIT_TEST_METRICS m2;
FLOAT x, y; // crashes if I skip these :/
hr = tl->layout->HitTestTextPosition(m.textPosition, trailing,
&x, &y, &m2);
if (hr != S_OK)
logHRESULT(L"error aligning trailing hit to nearest cluster", hr);
p = m2.textPosition + m2.length;
}
*pos = tl->u16tou8[p];
for (i = 0; i < tl->nLines; i++) {
double ltop, lbottom;
ltop = tl->lineInfo[i].y;
lbottom = ltop + tl->lineInfo[i].height;
// y will already >= ltop at this point since the past lbottom should == ltop
if (y < lbottom)
break;
}
if (i == tl->nLines)
i--;
*line = i;
}
double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)
{
BOOL trailing;
DWRITE_HIT_TEST_METRICS m;
FLOAT x, y;
HRESULT hr;
if (line < 0 || line >= tl->nLines)
return -1;
pos = tl->u8tou16[pos];
// note: >, not >=, because the position at endPos is valid!
if (pos < tl->lineInfo[line].startPos || pos > tl->lineInfo[line].endPos)
return -1;
// this behavior seems correct
// there's also PadWrite's TextEditor::GetCaretRect() but that requires state...
// TODO where does this fail?
trailing = FALSE;
if (pos != 0 && pos != tl->nUTF16 && pos == tl->lineInfo[line].endPos) {
pos--;
trailing = TRUE;
}
hr = tl->layout->HitTestTextPosition(pos, trailing,
&x, &y, &m);
if (hr != S_OK)
logHRESULT(L"error calling IDWriteTextLayout::HitTestTextPosition()", hr);
return x;
}
void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p)
{
DWORD caretWidth;
// there seems to be no defined caret color
// the best I can come up with is "inverts colors underneath" (according to https://msdn.microsoft.com/en-us/library/windows/desktop/ms648397(v=vs.85).aspx) which I have no idea how to do (TODO)
// just return black for now
p->r = 0.0;
p->g = 0.0;
p->b = 0.0;
p->a = 1.0;
if (SystemParametersInfoW(SPI_GETCARETWIDTH, 0, &caretWidth, 0) == 0)
// don't log the failure, fall back gracefully
// the instruction to use this comes from https://msdn.microsoft.com/en-us/library/windows/desktop/ms648399(v=vs.85).aspx
// and we have to assume GetSystemMetrics() always succeeds, so
caretWidth = GetSystemMetrics(SM_CXBORDER);
// TODO make this a function and split it out of areautil.cpp
{
FLOAT dpix, dpiy;
// TODO can we pass NULL for dpiy?
c->rt->GetDpi(&dpix, &dpiy);
// see https://msdn.microsoft.com/en-us/library/windows/desktop/dd756649%28v=vs.85%29.aspx (and others; search "direct2d mouse")
p->width = ((double) (caretWidth * 96)) / dpix;
}
// and there doesn't seem to be this either... (TODO check what PadWrite does?)
p->xoff = 0;
}

View File

@ -0,0 +1,912 @@
// 11 february 2017
#include "drawtext.h"
static uiAttributedString *attrstr;
#define nFeatures 256
static uiOpenTypeFeatures *features[nFeatures];
static int curFeature = 0;
static uiOpenTypeFeatures *addFeature(const char tag[4], uint32_t value)
{
uiOpenTypeFeatures *otf;
if (curFeature >= nFeatures) {
fprintf(stderr, "TODO (also TODO is there a panic function?)\n");
exit(EXIT_FAILURE);
}
otf = uiNewOpenTypeFeatures();
uiOpenTypeFeaturesAdd(otf, tag[0], tag[1], tag[2], tag[3], value);
features[curFeature] = otf;
curFeature++;
return otf;
}
// some of these examples come from Microsoft's and Apple's lists of typographic features and also https://www.fontfont.com/staticcontent/downloads/FF_OT_User_Guide.pdf
static void setupAttributedString(void)
{
uiAttributeSpec spec;
size_t start, end;
const char *next;
uiOpenTypeFeatures *otf;
int i;
attrstr = uiNewAttributedString("uiAttributedString isn't just for plain text! It supports ");
next = "multiple fonts";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFamily;
spec.Family = "Courier New";
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "multiple sizes";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeSize;
spec.Double = 18;
uiAttributedStringSetAttribute(attrstr,
&spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "multiple weights";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeWeight;
spec.Value = (uintptr_t) uiDrawTextWeightBold;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "multiple italics";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeItalic;
spec.Value = (uintptr_t) uiDrawTextItalicItalic;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "multiple stretches";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeStretch;
spec.Value = (uintptr_t) uiDrawTextStretchCondensed;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "multiple colors";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeColor;
// Direct2D "Crimson" (#DC143C)
spec.R = 0.8627450980392156;
spec.G = 0.0784313725490196;
spec.B = 0.2352941176470588;
spec.A = 0.75;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "multiple backgrounds";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeBackground;
// Direct2D "Peach Puff" (#FFDAB9)
// TODO choose a darker color
spec.R = 1.0;
spec.G = 0.85490196078431372;
spec.B = 0.7254901960784313;
spec.A = 0.5;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "multiple";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeUnderline;
spec.Value = uiDrawUnderlineStyleSingle;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " ");
next = "underlines";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeUnderline;
spec.Value = uiDrawUnderlineStyleDouble;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
spec.Type = uiAttributeUnderlineColor;
spec.Value = uiDrawUnderlineColorCustom;
spec.R = 0.5;
spec.G = 0.0;
spec.B = 1.0;
spec.A = 1.0;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " (");
next = "including underlines for spelling correction and the like";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeUnderline;
spec.Value = uiDrawUnderlineStyleSuggestion;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
spec.Type = uiAttributeUnderlineColor;
spec.Value = uiDrawUnderlineColorSpelling;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
// TODO randomize these ranges better
// TODO make some overlap to test that
// TODO also change colors to light foreground dark background
next = "or any combination of the above";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeWeight;
spec.Value = (uintptr_t) uiDrawTextWeightBold;
uiAttributedStringSetAttribute(attrstr, &spec, start, end - 8);
spec.Type = uiAttributeItalic;
spec.Value = (uintptr_t) uiDrawTextItalicItalic;
uiAttributedStringSetAttribute(attrstr, &spec, start + 3, end - 4);
spec.Type = uiAttributeColor;
spec.R = 0.8627450980392156;
spec.G = 0.0784313725490196;
spec.B = 0.2352941176470588;
spec.A = 0.75;
uiAttributedStringSetAttribute(attrstr, &spec, start + 12, end);
spec.Type = uiAttributeFamily;
spec.Family = "Helvetica";
uiAttributedStringSetAttribute(attrstr, &spec, start + 8, end - 1);
spec.Type = uiAttributeBackground;
spec.R = 1.0;
spec.G = 0.85490196078431372;
spec.B = 0.7254901960784313;
spec.A = 0.5;
uiAttributedStringSetAttribute(attrstr, &spec, start + 5, end - 7);
spec.Type = uiAttributeUnderline;
spec.Value = uiDrawUnderlineStyleSingle;
uiAttributedStringSetAttribute(attrstr, &spec, start + 9, end - 1);
// TODO rewrite this to talk about OpenTpe instead
// TODO also shorten this to something more useful and that covers the general gist of things (and combines features arbitrarily like the previous demo) when we add a general OpenType demo (see the last TODO in this function)
uiAttributedStringAppendUnattributed(attrstr, ". In addition, a variety of typographical features are available (depending on the chosen font) that can be switched on (or off, if the font enables them by default): ");
next = "fi";
uiAttributedStringAppendUnattributed(attrstr, "standard ligatures like f+i (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("liga", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
// note the use of LTR marks and RTL embeds to make sure the bidi algorithm doesn't kick in for our demonstration (it will produce incorrect results)
// see also: https://www.w3.org/International/articles/inline-bidi-markup/#nomarkup
next = "\xD9\x84\xD8\xA7";
uiAttributedStringAppendUnattributed(attrstr, "required ligatures like \xE2\x80\xAB\xD9\x84\xE2\x80\xAC+\xE2\x80\xAB\xD8\xA7\xE2\x80\xAC (\xE2\x80\x8E\xE2\x80\xAB");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("rlig", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, "\xE2\x80\xAC)");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "ct";
uiAttributedStringAppendUnattributed(attrstr, "discretionary/rare ligatures like c+t (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("dlig", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "the";
uiAttributedStringAppendUnattributed(attrstr, "contextual ligatures like h+e in the (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("clig", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
otf = addFeature("hlig", 1);
// This technically isn't what is meant by "historical ligatures", but Core Text's internal AAT-to-OpenType mapping says to include it, so we include it too
uiOpenTypeFeaturesAdd(otf, 'h', 'i', 's', 't', 1);
next = "\xC3\x9F";
uiAttributedStringAppendUnattributed(attrstr, "historical ligatures like the decomposition of \xC3\x9F (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = otf;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
// TODO a different word than "writing"?
next = "UnICasE wRITInG";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("unic", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "316";
uiAttributedStringAppendUnattributed(attrstr, "proportional (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("pnum", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ") and tabular/monospaced (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("tnum", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ") numbers");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "123";
uiAttributedStringAppendUnattributed(attrstr, "superscipts (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("sups", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "123";
uiAttributedStringAppendUnattributed(attrstr, "subscripts (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("subs", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "1st";
uiAttributedStringAppendUnattributed(attrstr, "ordinals (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("ordn", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "H2O";
uiAttributedStringAppendUnattributed(attrstr, "scientific inferiors (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("sinf", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "2/3";
uiAttributedStringAppendUnattributed(attrstr, "fraction forms (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
#if 0 /* TODO */
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFractionForms;
spec.Value = uiAttributeFractionFormNone;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ", ");
#endif
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("afrc", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ", ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("frac", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "0";
uiAttributedStringAppendUnattributed(attrstr, "slashed zeroes (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("zero", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("zero", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "\xCE\xA0\xCE\xA3";
uiAttributedStringAppendUnattributed(attrstr, "mathematical greek (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("mgrk", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("mgrk", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "qwertyuiop\xE2\x80\xA2";
uiAttributedStringAppendUnattributed(attrstr, "ornamental forms (");
for (i = 1; i < 11; i++) {
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("ornm", i);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
next = "\xE2\x80\xA2";
}
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "g";
uiAttributedStringAppendUnattributed(attrstr, "specific forms/alternates (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("aalt", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("aalt", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "ABCDEFGQWERTY";
uiAttributedStringAppendUnattributed(attrstr, "titling capital forms (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("titl", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("titl", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "\xE7\x80\x86";
uiAttributedStringAppendUnattributed(attrstr, "alternate Han character forms (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("jp78", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("jp83", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
otf = addFeature("onum", 0);
// Core Text's internal AAT-to-OpenType mapping says to include this, so we include it too
// TODO is it always set?
uiOpenTypeFeaturesAdd(otf, 'l', 'n', 'u', 'm', 0);
next = "0123456789";
uiAttributedStringAppendUnattributed(attrstr, "lowercase numbers (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = otf;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
otf = addFeature("onum", 1);
uiOpenTypeFeaturesAdd(otf, 'l', 'n', 'u', 'm', 1);
spec.Features = otf;
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "\xE4\xBC\xBD";
uiAttributedStringAppendUnattributed(attrstr, "hanja to hangul translation (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("hngl", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("hngl", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "\xE3\x81\x82";
uiAttributedStringAppendUnattributed(attrstr, "annotated glyph forms (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("nalt", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("nalt", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("nalt", 4); // AAT inverted circle
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "\xE3\x81\x82";
uiAttributedStringAppendUnattributed(attrstr, "ruby forms of kana (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("ruby", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("ruby", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "now is the time";
uiAttributedStringAppendUnattributed(attrstr, "italic forms of Latin letters in CJK fonts (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("ital", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("ital", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "{I} > {J}";
uiAttributedStringAppendUnattributed(attrstr, "case-sensitive character forms, such as punctuation (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("case", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("case", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "ABC";
uiAttributedStringAppendUnattributed(attrstr, "specialized spacing between uppercase letters (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("cpsp", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("cpsp", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "\xE3\x82\xB9\xE3\x83\x98\xE3\x83\x88";
uiAttributedStringAppendUnattributed(attrstr, "alternate horizontal (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("hkna", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("hkna", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ") and vertical (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("vkna", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("vkna", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ") kana forms");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "g";
uiAttributedStringAppendUnattributed(attrstr, "stylistic alternates (");
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
for (i = 1; i <= 20; i++) {
char tag[4];
tag[0] = 's';
tag[1] = 's';
tag[2] = '0';
if (i >= 10)
tag[2] = '1';
tag[3] = (i % 10) + '0'; // TODO see how I wrote this elsewhere
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Features = addFeature(tag, 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
}
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "first";
uiAttributedStringAppendUnattributed(attrstr, "contextual alternates (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("calt", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("calt", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "FONT";
uiAttributedStringAppendUnattributed(attrstr, "swashes (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("swsh", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("swsh", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "Font";
uiAttributedStringAppendUnattributed(attrstr, "contextual swashes (");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("cswh", 0);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, " vs. ");
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("cswh", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ")");
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "Small Caps";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("smcp", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "Petite Caps";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("pcap", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ", ");
next = "SMALL UPPERCASES";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("c2sp", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ", and ");
next = "PETITE UPPERCASES";
start = uiAttributedStringLen(attrstr);
end = start + strlen(next);
uiAttributedStringAppendUnattributed(attrstr, next);
spec.Type = uiAttributeFeatures;
spec.Features = addFeature("c2pc", 1);
uiAttributedStringSetAttribute(attrstr, &spec, start, end);
uiAttributedStringAppendUnattributed(attrstr, ".");
// TODO write a dedicated example for experimenting with typographic features like the one in gtk3-demo
}
static char fontFamily[] = "Times New Roman";
// TODO should be const; look at constructor function?
static uiDrawFontDescriptor defaultFont = {
.Family = fontFamily,
.Size = 12,
.Weight = uiDrawTextWeightNormal,
.Italic = uiDrawTextItalicNormal,
.Stretch = uiDrawTextStretchNormal,
};
static uiDrawTextLayoutParams params;
#define margins 10
static uiBox *panel;
static uiCheckbox *showLineBounds;
static uiFontButton *fontButton;
// TODO should be const?
static uiDrawBrush fillBrushes[4] = {
{
.Type = uiDrawBrushTypeSolid,
.R = 1.0,
.G = 0.0,
.B = 0.0,
.A = 0.5,
},
{
.Type = uiDrawBrushTypeSolid,
.R = 0.0,
.G = 1.0,
.B = 0.0,
.A = 0.5,
},
{
.Type = uiDrawBrushTypeSolid,
.R = 0.0,
.G = 0.0,
.B = 1.0,
.A = 0.5,
},
{
.Type = uiDrawBrushTypeSolid,
.R = 0.0,
.G = 1.0,
.B = 1.0,
.A = 0.5,
},
};
static void draw(uiAreaDrawParams *p)
{
uiDrawPath *path;
uiDrawTextLayout *layout;
uiDrawBrush b;
b.Type = uiDrawBrushTypeSolid;
// only clip the text, not the guides
uiDrawSave(p->Context);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathAddRectangle(path, margins, margins,
p->AreaWidth - 2 * margins,
p->AreaHeight - 2 * margins);
uiDrawPathEnd(path);
uiDrawClip(p->Context, path);
uiDrawFreePath(path);
params.Width = p->AreaWidth - 2 * margins;
layout = uiDrawNewTextLayout(&params);
uiDrawText(p->Context, layout, margins, margins);
uiDrawRestore(p->Context);
if (uiCheckboxChecked(showLineBounds)) {
uiDrawTextLayoutLineMetrics m;
int i, n;
int fill = 0;
n = uiDrawTextLayoutNumLines(layout);
for (i = 0; i < n; i++) {
uiDrawTextLayoutLineGetMetrics(layout, i, &m);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathAddRectangle(path, margins + m.X, margins + m.Y,
m.Width, m.Height);
uiDrawPathEnd(path);
uiDrawFill(p->Context, path, fillBrushes + fill);
uiDrawFreePath(path);
fill = (fill + 1) % 4;
}
}
uiDrawFreeTextLayout(layout);
}
static struct example attributesExample;
static void changeFont(uiFontButton *b, void *data)
{
if (defaultFont.Family != fontFamily)
uiFreeText(defaultFont.Family);
// TODO rename defaultFont
uiFontButtonFont(fontButton, &defaultFont);
redraw();
}
// TODO share?
static void checkboxChecked(uiCheckbox *c, void *data)
{
redraw();
}
static uiCheckbox *newCheckbox(const char *text)
{
uiCheckbox *c;
c = uiNewCheckbox(text);
uiCheckboxOnToggled(c, checkboxChecked, NULL);
uiBoxAppend(panel, uiControl(c), 0);
return c;
}
struct example *mkAttributesExample(void)
{
panel = uiNewVerticalBox();
showLineBounds = newCheckbox("Show Line Bounds");
fontButton = uiNewFontButton();
uiFontButtonOnChanged(fontButton, changeFont, NULL);
// TODO set the font button to the current defaultFont
uiBoxAppend(panel, uiControl(fontButton), 0);
attributesExample.name = "Attributed Text";
attributesExample.panel = uiControl(panel);
attributesExample.draw = draw;
attributesExample.mouse = NULL;
attributesExample.key = NULL;
setupAttributedString();
params.String = attrstr;
params.DefaultFont = &defaultFont;
params.Align = uiDrawTextAlignLeft;
return &attributesExample;
}

View File

@ -0,0 +1,267 @@
// 17 january 2017
#include "drawtext.h"
static const char *text =
"It is with a kind of fear that I begin to write the history of my life. "
"I have, as it were, a superstitious hesitation in lifting the veil that "
"clings about my childhood like a golden mist. The task of writing an "
"autobiography is a difficult one. When I try to classify my earliest "
"impressions, I find that fact and fancy look alike across the years that "
"link the past with the present. The woman paints the child's experiences "
"in her own fantasy. A few impressions stand out vividly from the first "
"years of my life; but \"the shadows of the prison-house are on the rest.\" "
"Besides, many of the joys and sorrows of childhood have lost their "
"poignancy; and many incidents of vital importance in my early education "
"have been forgotten in the excitement of great discoveries. In order, "
"therefore, not to be tedious I shall try to present in a series of "
"sketches only the episodes that seem to me to be the most interesting "
"and important."
"";
static char fontFamily[] = "Palatino";
// TODO should be const; look at constructor function?
static uiDrawFontDescriptor defaultFont = {
.Family = fontFamily,
.Size = 18,
.Weight = uiDrawTextWeightNormal,
.Italic = uiDrawTextItalicNormal,
.Stretch = uiDrawTextStretchNormal,
};
static uiAttributedString *attrstr;
static uiDrawTextLayoutParams params;
#define margins 10
static uiBox *panel;
static uiCheckbox *showExtents;
static uiCheckbox *showLineBounds;
static uiCheckbox *showLineGuides;
// TODO should this be const?
static double strokeDashes[] = { 5, 2 };
// TODO this should be const
static uiDrawStrokeParams strokeParams = {
.Cap = uiDrawLineCapFlat,
.Join = uiDrawLineJoinMiter,
.Thickness = 1,
.MiterLimit = uiDrawDefaultMiterLimit,
.Dashes = strokeDashes,
.NumDashes = 2,
.DashPhase = 0,
};
// TODO should be const?
static uiDrawBrush fillBrushes[4] = {
{
.Type = uiDrawBrushTypeSolid,
.R = 1.0,
.G = 0.0,
.B = 0.0,
.A = 0.5,
},
{
.Type = uiDrawBrushTypeSolid,
.R = 0.0,
.G = 1.0,
.B = 0.0,
.A = 0.5,
},
{
.Type = uiDrawBrushTypeSolid,
.R = 0.0,
.G = 0.0,
.B = 1.0,
.A = 0.5,
},
{
.Type = uiDrawBrushTypeSolid,
.R = 0.0,
.G = 1.0,
.B = 1.0,
.A = 0.5,
},
};
// TODO come up with better colors
static uiDrawBrush strokeBrushes[3] = {
// baseline
{
.Type = uiDrawBrushTypeSolid,
.R = 0.5,
.G = 0.5,
.B = 0.0,
.A = 0.75,
},
// ascent
{
.Type = uiDrawBrushTypeSolid,
.R = 1.0,
.G = 0.0,
.B = 1.0,
.A = 0.75,
},
// descent
{
.Type = uiDrawBrushTypeSolid,
.R = 0.5,
.G = 0.75,
.B = 1.0,
.A = 0.75,
},
};
static void draw(uiAreaDrawParams *p)
{
uiDrawPath *path;
uiDrawTextLayout *layout;
uiDrawBrush b;
b.Type = uiDrawBrushTypeSolid;
// only clip the text, not the guides
uiDrawSave(p->Context);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathAddRectangle(path, margins, margins,
p->AreaWidth - 2 * margins,
p->AreaHeight - 2 * margins);
uiDrawPathEnd(path);
uiDrawClip(p->Context, path);
uiDrawFreePath(path);
// TODO get rid of this later
#if 0
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathAddRectangle(path, -100, -100,
p->AreaWidth * 2,
p->AreaHeight * 2);
uiDrawPathEnd(path);
b.R = 0.0;
b.G = 1.0;
b.B = 0.0;
b.A = 1.0;
uiDrawFill(p->Context, path, &b);
uiDrawFreePath(path);
#endif
params.Width = p->AreaWidth - 2 * margins;
layout = uiDrawNewTextLayout(&params);
uiDrawText(p->Context, layout, margins, margins);
uiDrawRestore(p->Context);
if (uiCheckboxChecked(showExtents)) {
double width, height;
uiDrawTextLayoutExtents(layout, &width, &height);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathAddRectangle(path, margins, margins,
width, height);
uiDrawPathEnd(path);
b.R = 1.0;
b.G = 0.0;
b.B = 1.0;
b.A = 0.5;
uiDrawStroke(p->Context, path, &b, &strokeParams);
uiDrawFreePath(path);
}
if (uiCheckboxChecked(showLineBounds) || uiCheckboxChecked(showLineGuides)) {
uiDrawTextLayoutLineMetrics m;
int i, n;
int fill = 0;
n = uiDrawTextLayoutNumLines(layout);
for (i = 0; i < n; i++) {
uiDrawTextLayoutLineGetMetrics(layout, i, &m);
if (uiCheckboxChecked(showLineBounds)) {
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathAddRectangle(path, margins + m.X, margins + m.Y,
m.Width, m.Height);
uiDrawPathEnd(path);
uiDrawFill(p->Context, path, fillBrushes + fill);
uiDrawFreePath(path);
fill = (fill + 1) % 4;
}
if (uiCheckboxChecked(showLineGuides)) {
// baseline
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathNewFigure(path,
margins + m.X,
margins + m.BaselineY);
uiDrawPathLineTo(path,
margins + m.X + m.Width,
margins + m.BaselineY);
uiDrawPathEnd(path);
uiDrawStroke(p->Context, path, &(strokeBrushes[0]), &strokeParams);
uiDrawFreePath(path);
// ascent line
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathNewFigure(path,
margins + m.X,
margins + m.BaselineY - m.Ascent);
uiDrawPathLineTo(path,
margins + m.X + m.Width,
margins + m.BaselineY - m.Ascent);
uiDrawPathEnd(path);
uiDrawStroke(p->Context, path, &(strokeBrushes[1]), &strokeParams);
uiDrawFreePath(path);
// descent line
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathNewFigure(path,
margins + m.X,
margins + m.BaselineY + m.Descent);
uiDrawPathLineTo(path,
margins + m.X + m.Width,
margins + m.BaselineY + m.Descent);
uiDrawPathEnd(path);
uiDrawStroke(p->Context, path, &(strokeBrushes[2]), &strokeParams);
uiDrawFreePath(path);
}
}
}
uiDrawFreeTextLayout(layout);
}
static struct example basicExample;
// TODO share?
static void checkboxChecked(uiCheckbox *c, void *data)
{
redraw();
}
static uiCheckbox *newCheckbox(const char *text)
{
uiCheckbox *c;
c = uiNewCheckbox(text);
uiCheckboxOnToggled(c, checkboxChecked, NULL);
uiBoxAppend(panel, uiControl(c), 0);
return c;
}
struct example *mkBasicExample(void)
{
panel = uiNewVerticalBox();
showExtents = newCheckbox("Show Layout Extents");
showLineBounds = newCheckbox("Show Line Bounds");
showLineGuides = newCheckbox("Show Line Guides");
basicExample.name = "Basic Paragraph of Text";
basicExample.panel = uiControl(panel);
basicExample.draw = draw;
basicExample.mouse = NULL;
basicExample.key = NULL;
attrstr = uiNewAttributedString(text);
params.String = attrstr;
params.DefaultFont = &defaultFont;
params.Align = uiDrawTextAlignLeft;
return &basicExample;
}
// TODO on GTK+ an area by itself in a window doesn't get expand properties set properly?

View File

@ -0,0 +1,29 @@
// 20 january 2017
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "../../ui.h"
struct example {
const char *name;
uiControl *panel;
void (*draw)(uiAreaDrawParams *p);
void (*mouse)(uiAreaMouseEvent *e);
int (*key)(uiAreaKeyEvent *e);
// TODO key?
};
// main.c
extern void redraw(void);
// basic.c
extern struct example *mkBasicExample(void);
// hittest.c
extern struct example *mkHitTestExample(void);
// attributes.c
extern struct example *mkAttributesExample(void);
// emptystr_hittest.c
extern struct example *mkEmptyStringExample(void);

View File

@ -0,0 +1,254 @@
// 20 january 2017
#include "drawtext.h"
// TODO FOR THIS FILE
// get rid of it once we rewrite this example (which requires having more fine-grained scrolling control)
// TODO double-check ligatures on all platforms to make sure we can place the cursor at the right place
// TODO the hiding and showing does not work properly on GTK+
// TODO using the arrow keys allows us to walk back to the end of the line on some platforms (TODO which?); IIRC arrow keys shouldn't do that
// TODO make sure to check the cursor positions of RTL on all platforms
static const char *text = "";
static char fontFamily[] = "Helvetica";
static uiDrawFontDescriptor defaultFont = {
.Family = fontFamily,
.Size = 14,
.Weight = uiDrawTextWeightNormal,
.Italic = uiDrawTextItalicNormal,
.Stretch = uiDrawTextStretchNormal,
};
static uiAttributedString *attrstr;
static uiDrawTextLayoutParams params;
#define margins 10
static uiBox *panel;
static uiBox *vbox;
static uiLabel *caretLabel;
static uiCheckbox *showLineBounds;
static uiFontButton *fontButton;
static uiCombobox *textAlign;
static int caretLine = -1;
static size_t caretPos;
// TODO should be const?
static uiDrawBrush fillBrushes[4] = {
{
.Type = uiDrawBrushTypeSolid,
.R = 1.0,
.G = 0.0,
.B = 0.0,
.A = 0.5,
},
{
.Type = uiDrawBrushTypeSolid,
.R = 0.0,
.G = 1.0,
.B = 0.0,
.A = 0.5,
},
{
.Type = uiDrawBrushTypeSolid,
.R = 0.0,
.G = 0.0,
.B = 1.0,
.A = 0.5,
},
{
.Type = uiDrawBrushTypeSolid,
.R = 0.0,
.G = 1.0,
.B = 1.0,
.A = 0.5,
},
};
static void draw(uiAreaDrawParams *p)
{
uiDrawPath *path;
uiDrawTextLayout *layout;
uiDrawTextLayoutLineMetrics m;
// only clip the text, not the guides
uiDrawSave(p->Context);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathAddRectangle(path, margins, margins,
p->AreaWidth - 2 * margins,
p->AreaHeight - 2 * margins);
uiDrawPathEnd(path);
uiDrawClip(p->Context, path);
uiDrawFreePath(path);
params.Width = p->AreaWidth - 2 * margins;
layout = uiDrawNewTextLayout(&params);
uiDrawText(p->Context, layout, margins, margins);
uiDrawRestore(p->Context);
if (caretLine == -1) {
caretLine = uiDrawTextLayoutNumLines(layout) - 1;
caretPos = uiAttributedStringLen(attrstr);
}
uiDrawCaret(p->Context, margins, margins,
layout, caretPos, &caretLine);
if (uiCheckboxChecked(showLineBounds)) {
int i, n;
int fill = 0;
n = uiDrawTextLayoutNumLines(layout);
for (i = 0; i < n; i++) {
uiDrawTextLayoutLineGetMetrics(layout, i, &m);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathAddRectangle(path, margins + m.X, margins + m.Y,
m.Width, m.Height);
uiDrawPathEnd(path);
uiDrawFill(p->Context, path, fillBrushes + fill);
uiDrawFreePath(path);
fill = (fill + 1) % 4;
}
}
uiDrawFreeTextLayout(layout);
}
static void mouse(uiAreaMouseEvent *e)
{
uiDrawTextLayout *layout;
char labelText[128];
if (e->Down != 1)
return;
params.Width = e->AreaWidth - 2 * margins;
layout = uiDrawNewTextLayout(&params);
uiDrawTextLayoutHitTest(layout,
e->X - margins, e->Y - margins,
&caretPos, &caretLine);
uiDrawFreeTextLayout(layout);
// TODO move this into the draw handler so it is reflected by keyboard-based position changes
// urgh %zd is not supported by MSVC with sprintf()
// TODO get that part in test/ about having no other option
sprintf(labelText, "pos %d line %d",
(int) caretPos, caretLine);
uiLabelSetText(caretLabel, labelText);
redraw();
}
static int key(uiAreaKeyEvent *e)
{
size_t grapheme;
if (e->Up)
return 0;
if (e->Key != 0)
return 0;
switch (e->ExtKey) {
case uiExtKeyUp:
// TODO
return 1;
case uiExtKeyDown:
// TODO
return 1;
case uiExtKeyLeft:
grapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos);
if (grapheme == 0)
return 0;
grapheme--;
caretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme);
redraw();
return 1;
case uiExtKeyRight:
grapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos);
if (grapheme == uiAttributedStringNumGraphemes(attrstr))
return 0;
grapheme++;
caretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme);
redraw();
return 1;
}
return 0;
}
static struct example hitTestExample;
// TODO share?
static void checkboxChecked(uiCheckbox *c, void *data)
{
redraw();
}
static void changeFont(uiFontButton *b, void *data)
{
if (defaultFont.Family != fontFamily)
uiFreeText(defaultFont.Family);
// TODO rename defaultFont
uiFontButtonFont(fontButton, &defaultFont);
printf("{\n\tfamily: %s\n\tsize: %g\n\tweight: %d\n\titalic: %d\n\tstretch: %d\n}\n",
defaultFont.Family,
defaultFont.Size,
(int) (defaultFont.Weight),
(int) (defaultFont.Italic),
(int) (defaultFont.Stretch));
redraw();
}
static void changeTextAlign(uiCombobox *c, void *data)
{
// note the order of the items added below
params.Align = (uiDrawTextAlign) uiComboboxSelected(textAlign);
redraw();
}
// TODO share?
static uiCheckbox *newCheckbox(uiBox *box, const char *text)
{
uiCheckbox *c;
c = uiNewCheckbox(text);
uiCheckboxOnToggled(c, checkboxChecked, NULL);
uiBoxAppend(box, uiControl(c), 0);
return c;
}
struct example *mkEmptyStringExample(void)
{
panel = uiNewHorizontalBox();
vbox = uiNewVerticalBox();
// TODO the second vbox causes this not to stretch at least on OS X
uiBoxAppend(panel, uiControl(vbox), 1);
caretLabel = uiNewLabel("Caret information is shown here");
uiBoxAppend(vbox, uiControl(caretLabel), 0);
showLineBounds = newCheckbox(vbox, "Show Line Bounds (for debugging metrics)");
vbox = uiNewVerticalBox();
uiBoxAppend(panel, uiControl(vbox), 0);
fontButton = uiNewFontButton();
uiFontButtonOnChanged(fontButton, changeFont, NULL);
// TODO set the font button to the current defaultFont
uiBoxAppend(vbox, uiControl(fontButton), 0);
textAlign = uiNewCombobox();
// note that these are in the order in the enum
uiComboboxAppend(textAlign, "Left");
uiComboboxAppend(textAlign, "Center");
uiComboboxAppend(textAlign, "Right");
uiComboboxOnSelected(textAlign, changeTextAlign, NULL);
uiBoxAppend(vbox, uiControl(textAlign), 0);
hitTestExample.name = "Empty String";
hitTestExample.panel = uiControl(panel);
hitTestExample.draw = draw;
hitTestExample.mouse = mouse;
hitTestExample.key = key;
attrstr = uiNewAttributedString(text);
params.String = attrstr;
params.DefaultFont = &defaultFont;
params.Align = uiDrawTextAlignLeft;
return &hitTestExample;
}

View File

@ -0,0 +1,262 @@
// 20 january 2017
#include "drawtext.h"
// TODO double-check ligatures on all platforms to make sure we can place the cursor at the right place
// TODO using the arrow keys allows us to walk back to the end of the line on some platforms (TODO which?); IIRC arrow keys shouldn't do that
// TODO make sure to check the cursor positions of RTL on all platforms
static const char *text =
"Each of the glyphs an end user interacts with are called graphemes. "
"If you enter a byte range in the text boxes below and click the button, you can see the blue box move to surround that byte range, as well as what the actual byte range necessary is. "
// TODO rephrase this; I don't think this code will use those grapheme functions...
"You'll also see the index of the first grapheme; uiAttributedString has facilities for converting between UTF-8 code points and grapheme indices. "
"Additionally, click on the string to move the caret. Watch the status text at the bottom change too. "
"That being said: "
"\xC3\x93O\xCC\x81 (combining accents) "
"A\xCC\xAA\xEF\xB8\xA0 (multiple combining characters) "
"\xE2\x80\xAE#\xE2\x80\xAC (RTL glyph) "
"\xF0\x9F\x92\xBB (non-BMP character) "
"\xF0\x9F\x92\xBB\xCC\x80 (combined non-BMP character; may render strangely) "
"";
static char fontFamily[] = "Helvetica";
static uiDrawFontDescriptor defaultFont = {
.Family = fontFamily,
.Size = 14,
.Weight = uiDrawTextWeightNormal,
.Italic = uiDrawTextItalicNormal,
.Stretch = uiDrawTextStretchNormal,
};
static uiAttributedString *attrstr;
static uiDrawTextLayoutParams params;
#define margins 10
static uiBox *panel;
static uiBox *vbox;
static uiLabel *caretLabel;
static uiCheckbox *showLineBounds;
static uiFontButton *fontButton;
static uiCombobox *textAlign;
static int caretLine = -1;
static size_t caretPos;
// TODO should be const?
static uiDrawBrush fillBrushes[4] = {
{
.Type = uiDrawBrushTypeSolid,
.R = 1.0,
.G = 0.0,
.B = 0.0,
.A = 0.5,
},
{
.Type = uiDrawBrushTypeSolid,
.R = 0.0,
.G = 1.0,
.B = 0.0,
.A = 0.5,
},
{
.Type = uiDrawBrushTypeSolid,
.R = 0.0,
.G = 0.0,
.B = 1.0,
.A = 0.5,
},
{
.Type = uiDrawBrushTypeSolid,
.R = 0.0,
.G = 1.0,
.B = 1.0,
.A = 0.5,
},
};
static void draw(uiAreaDrawParams *p)
{
uiDrawPath *path;
uiDrawTextLayout *layout;
uiDrawTextLayoutLineMetrics m;
// only clip the text, not the guides
uiDrawSave(p->Context);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathAddRectangle(path, margins, margins,
p->AreaWidth - 2 * margins,
p->AreaHeight - 2 * margins);
uiDrawPathEnd(path);
uiDrawClip(p->Context, path);
uiDrawFreePath(path);
params.Width = p->AreaWidth - 2 * margins;
layout = uiDrawNewTextLayout(&params);
uiDrawText(p->Context, layout, margins, margins);
uiDrawRestore(p->Context);
if (caretLine == -1) {
caretLine = uiDrawTextLayoutNumLines(layout) - 1;
caretPos = uiAttributedStringLen(attrstr);
}
uiDrawCaret(p->Context, margins, margins,
layout, caretPos, &caretLine);
if (uiCheckboxChecked(showLineBounds)) {
int i, n;
int fill = 0;
n = uiDrawTextLayoutNumLines(layout);
for (i = 0; i < n; i++) {
uiDrawTextLayoutLineGetMetrics(layout, i, &m);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathAddRectangle(path, margins + m.X, margins + m.Y,
m.Width, m.Height);
uiDrawPathEnd(path);
uiDrawFill(p->Context, path, fillBrushes + fill);
uiDrawFreePath(path);
fill = (fill + 1) % 4;
}
}
uiDrawFreeTextLayout(layout);
}
static void mouse(uiAreaMouseEvent *e)
{
uiDrawTextLayout *layout;
char labelText[128];
if (e->Down != 1)
return;
params.Width = e->AreaWidth - 2 * margins;
layout = uiDrawNewTextLayout(&params);
uiDrawTextLayoutHitTest(layout,
e->X - margins, e->Y - margins,
&caretPos, &caretLine);
uiDrawFreeTextLayout(layout);
// TODO move this into the draw handler so it is reflected by keyboard-based position changes
// urgh %zd is not supported by MSVC with sprintf()
// TODO get that part in test/ about having no other option
sprintf(labelText, "pos %d line %d",
(int) caretPos, caretLine);
uiLabelSetText(caretLabel, labelText);
redraw();
}
static int key(uiAreaKeyEvent *e)
{
size_t grapheme;
if (e->Up)
return 0;
if (e->Key != 0)
return 0;
switch (e->ExtKey) {
case uiExtKeyUp:
// TODO
return 1;
case uiExtKeyDown:
// TODO
return 1;
case uiExtKeyLeft:
grapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos);
if (grapheme == 0)
return 0;
grapheme--;
caretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme);
redraw();
return 1;
case uiExtKeyRight:
grapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos);
if (grapheme == uiAttributedStringNumGraphemes(attrstr))
return 0;
grapheme++;
caretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme);
redraw();
return 1;
}
return 0;
}
static struct example hitTestExample;
// TODO share?
static void checkboxChecked(uiCheckbox *c, void *data)
{
redraw();
}
static void changeFont(uiFontButton *b, void *data)
{
if (defaultFont.Family != fontFamily)
uiFreeText(defaultFont.Family);
// TODO rename defaultFont
uiFontButtonFont(fontButton, &defaultFont);
printf("{\n\tfamily: %s\n\tsize: %g\n\tweight: %d\n\titalic: %d\n\tstretch: %d\n}\n",
defaultFont.Family,
defaultFont.Size,
(int) (defaultFont.Weight),
(int) (defaultFont.Italic),
(int) (defaultFont.Stretch));
redraw();
}
static void changeTextAlign(uiCombobox *c, void *data)
{
// note the order of the items added below
params.Align = (uiDrawTextAlign) uiComboboxSelected(textAlign);
redraw();
}
// TODO share?
static uiCheckbox *newCheckbox(uiBox *box, const char *text)
{
uiCheckbox *c;
c = uiNewCheckbox(text);
uiCheckboxOnToggled(c, checkboxChecked, NULL);
uiBoxAppend(box, uiControl(c), 0);
return c;
}
struct example *mkHitTestExample(void)
{
panel = uiNewHorizontalBox();
vbox = uiNewVerticalBox();
// TODO the second vbox causes this not to stretch at least on OS X
uiBoxAppend(panel, uiControl(vbox), 1);
caretLabel = uiNewLabel("Caret information is shown here");
uiBoxAppend(vbox, uiControl(caretLabel), 0);
showLineBounds = newCheckbox(vbox, "Show Line Bounds (for debugging metrics)");
vbox = uiNewVerticalBox();
uiBoxAppend(panel, uiControl(vbox), 0);
fontButton = uiNewFontButton();
uiFontButtonOnChanged(fontButton, changeFont, NULL);
// TODO set the font button to the current defaultFont
uiBoxAppend(vbox, uiControl(fontButton), 0);
textAlign = uiNewCombobox();
// note that these are in the order in the enum
uiComboboxAppend(textAlign, "Left");
uiComboboxAppend(textAlign, "Center");
uiComboboxAppend(textAlign, "Right");
uiComboboxOnSelected(textAlign, changeTextAlign, NULL);
uiBoxAppend(vbox, uiControl(textAlign), 0);
hitTestExample.name = "Hit-Testing and Grapheme Boundaries";
hitTestExample.panel = uiControl(panel);
hitTestExample.draw = draw;
hitTestExample.mouse = mouse;
hitTestExample.key = key;
attrstr = uiNewAttributedString(text);
params.String = attrstr;
params.DefaultFont = &defaultFont;
params.Align = uiDrawTextAlignLeft;
return &hitTestExample;
}

Binary file not shown.

View File

@ -0,0 +1,136 @@
// 17 january 2017
#include "drawtext.h"
static uiWindow *mainwin;
static uiBox *box;
static uiCombobox *exampleList;
static uiArea *area;
static uiAreaHandler handler;
#define nExamples 20
static struct example *examples[nExamples];
static int curExample = 0;
static void onExampleChanged(uiCombobox *c, void *data)
{
uiControlHide(examples[curExample]->panel);
curExample = uiComboboxSelected(exampleList);
uiControlShow(examples[curExample]->panel);
redraw();
}
void redraw(void)
{
uiAreaQueueRedrawAll(area);
}
static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)
{
examples[curExample]->draw(p);
}
static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)
{
if (examples[curExample]->mouse != NULL)
examples[curExample]->mouse(e);
}
static void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)
{
// do nothing
}
static void handlerDragBroken(uiAreaHandler *ah, uiArea *a)
{
// do nothing
}
static int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)
{
if (examples[curExample]->key != NULL)
return examples[curExample]->key(e);
return 0;
}
static int onClosing(uiWindow *w, void *data)
{
uiControlDestroy(uiControl(mainwin));
uiQuit();
return 0;
}
static int shouldQuit(void *data)
{
uiControlDestroy(uiControl(mainwin));
return 1;
}
int main(void)
{
uiInitOptions o;
const char *err;
int n;
handler.Draw = handlerDraw;
handler.MouseEvent = handlerMouseEvent;
handler.MouseCrossed = handlerMouseCrossed;
handler.DragBroken = handlerDragBroken;
handler.KeyEvent = handlerKeyEvent;
memset(&o, 0, sizeof (uiInitOptions));
err = uiInit(&o);
if (err != NULL) {
fprintf(stderr, "error initializing ui: %s\n", err);
uiFreeInitError(err);
return 1;
}
uiOnShouldQuit(shouldQuit, NULL);
mainwin = uiNewWindow("libui Text-Drawing Example", 640, 480, 1);
uiWindowOnClosing(mainwin, onClosing, NULL);
box = uiNewVerticalBox();
uiWindowSetChild(mainwin, uiControl(box));
exampleList = uiNewCombobox();
uiBoxAppend(box, uiControl(exampleList), 0);
area = uiNewArea(&handler);
uiBoxAppend(box, uiControl(area), 1);
n = 0;
examples[n] = mkBasicExample();
uiComboboxAppend(exampleList, examples[n]->name);
uiControlHide(examples[n]->panel);
uiBoxAppend(box, examples[n]->panel, 0);
n++;
examples[n] = mkHitTestExample();
uiComboboxAppend(exampleList, examples[n]->name);
uiControlHide(examples[n]->panel);
uiBoxAppend(box, examples[n]->panel, 0);
n++;
examples[n] = mkAttributesExample();
uiComboboxAppend(exampleList, examples[n]->name);
uiControlHide(examples[n]->panel);
uiBoxAppend(box, examples[n]->panel, 0);
n++;
examples[n] = mkEmptyStringExample();
uiComboboxAppend(exampleList, examples[n]->name);
uiControlHide(examples[n]->panel);
uiBoxAppend(box, examples[n]->panel, 0);
n++;
// and set things up for the initial state
uiComboboxSetSelected(exampleList, 0);
uiComboboxOnSelected(exampleList, onExampleChanged, NULL);
// and set up the first one
onExampleChanged(NULL, NULL);
uiControlShow(uiControl(mainwin));
uiMain();
// TODO free examples
uiUninit();
return 0;
}

View File

@ -0,0 +1,58 @@
# 3 june 2016
if(WIN32)
set(_EXAMPLE_RESOURCES_RC resources.rc)
endif()
macro(_add_example _name)
_add_exec(${_name} ${ARGN})
# because Microsoft's toolchain is dumb
if(MSVC)
set_property(TARGET ${_name} APPEND_STRING PROPERTY
LINK_FLAGS " /ENTRY:mainCRTStartup")
endif()
endmacro()
_add_example(controlgallery
controlgallery/main.c
${_EXAMPLE_RESOURCES_RC}
)
_add_example(histogram
histogram/main.c
${_EXAMPLE_RESOURCES_RC}
)
_add_example(cpp-multithread
cpp-multithread/main.cpp
${_EXAMPLE_RESOURCES_RC}
)
if(NOT WIN32)
target_link_libraries(cpp-multithread pthread)
endif()
_add_example(drawtext
drawtext/attributes.c
drawtext/basic.c
drawtext/emptystr_hittest.c
drawtext/hittest.c
drawtext/main.c
${_EXAMPLE_RESOURCES_RC}
)
target_include_directories(drawtext
PRIVATE drawtext)
_add_example(opentype
opentype/main.c
${_EXAMPLE_RESOURCES_RC}
)
target_include_directories(opentype
PRIVATE opentype)
add_custom_target(examples
DEPENDS
controlgallery
histogram
cpp-multithread
drawtext
opentype)

View File

@ -0,0 +1,201 @@
// 10 june 2017
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "../../ui.h"
// TODO the grid simply flat out does not work on OS X
uiWindow *mainwin;
uiFontButton *fontButton;
uiEntry *textEntry;
uiCheckbox *nullFeatures;
uiArea *area;
uiAttributedString *attrstr = NULL;
static void remakeAttrStr(void)
{
char *text;
uiOpenTypeFeatures *otf;
uiAttributeSpec spec;
if (attrstr != NULL)
uiFreeAttributedString(attrstr);
text = uiEntryText(textEntry);
attrstr = uiNewAttributedString(text);
uiFreeText(text);
if (!uiCheckboxChecked(nullFeatures)) {
otf = uiNewOpenTypeFeatures();
// TODO
spec.Type = uiAttributeFeatures;
spec.Features = otf;
uiAttributedStringSetAttribute(attrstr, &spec,
0, uiAttributedStringLen(attrstr));
// and uiAttributedString copied otf
uiFreeOpenTypeFeatures(otf);
}
uiAreaQueueRedrawAll(area);
}
// TODO make a variable of main()? in all programs?
static uiAreaHandler handler;
static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)
{
uiDrawTextLayout *layout;
uiDrawTextLayoutParams lp;
uiDrawFontDescriptor desc;
memset(&lp, 0, sizeof (uiDrawTextLayoutParams));
lp.String = attrstr;
uiFontButtonFont(fontButton, &desc);
lp.DefaultFont = &desc;
lp.Width = p->AreaWidth;
lp.Align = uiDrawTextAlignLeft;
layout = uiDrawNewTextLayout(&lp);
uiDrawText(p->Context, layout, 0, 0);
uiDrawFreeTextLayout(layout);
}
static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)
{
// do nothing
}
static void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)
{
// do nothing
}
static void handlerDragBroken(uiAreaHandler *ah, uiArea *a)
{
// do nothing
}
static int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)
{
// reject all keys
return 0;
}
static void onFontChanged(uiFontButton *b, void *data)
{
remakeAttrStr();
}
static void onTextChanged(uiEntry *e, void *data)
{
remakeAttrStr();
}
static void onNULLToggled(uiCheckbox *c, void *data)
{
remakeAttrStr();
}
static int onClosing(uiWindow *w, void *data)
{
// TODO change the others to be like this? (the others destroy here rather than later)
// TODO move this below uiQuit()?
uiControlHide(uiControl(w));
uiQuit();
return 0;
}
static int shouldQuit(void *data)
{
uiControlDestroy(uiControl(mainwin));
return 1;
}
int main(void)
{
uiInitOptions o;
const char *err;
uiGrid *grid;
uiBox *vbox;
handler.Draw = handlerDraw;
handler.MouseEvent = handlerMouseEvent;
handler.MouseCrossed = handlerMouseCrossed;
handler.DragBroken = handlerDragBroken;
handler.KeyEvent = handlerKeyEvent;
memset(&o, 0, sizeof (uiInitOptions));
err = uiInit(&o);
if (err != NULL) {
fprintf(stderr, "error initializing ui: %s\n", err);
uiFreeInitError(err);
return 1;
}
uiOnShouldQuit(shouldQuit, NULL);
// TODO 800x600? the size of the GTK+ example?
mainwin = uiNewWindow("libui OpenType Features Example", 640, 480, 1);
uiWindowSetMargined(mainwin, 1);
uiWindowOnClosing(mainwin, onClosing, NULL);
grid = uiNewGrid();
uiGridSetPadded(grid, 1);
uiWindowSetChild(mainwin, uiControl(grid));
fontButton = uiNewFontButton();
uiFontButtonOnChanged(fontButton, onFontChanged, NULL);
uiGridAppend(grid, uiControl(fontButton),
0, 0, 1, 1,
// TODO are these Y values correct?
0, uiAlignFill, 0, uiAlignCenter);
textEntry = uiNewEntry();
uiEntrySetText(textEntry, "afford afire aflight");
uiEntryOnChanged(textEntry, onTextChanged, NULL);
uiGridAppend(grid, uiControl(textEntry),
1, 0, 1, 1,
// TODO are these Y values correct too?
// TODO add a baseline align? or a form align?
1, uiAlignFill, 0, uiAlignCenter);
vbox = uiNewVerticalBox();
uiBoxSetPadded(vbox, 1);
uiGridAppend(grid, uiControl(vbox),
0, 1, 1, 1,
0, uiAlignFill, 1, uiAlignFill);
nullFeatures = uiNewCheckbox("NULL uiOpenTypeFeatures");
uiCheckboxOnToggled(nullFeatures, onNULLToggled, NULL);
uiBoxAppend(vbox, uiControl(nullFeatures), 0);
// TODO separator (if other stuff isn't a tab)
// TODO needed for this to be testable on os x without rewriting everything again
{
int x;
for (x = 0; x < 10; x++)
uiBoxAppend(vbox, uiControl(uiNewEntry()), 0);
}
// TODO other stuff
area = uiNewArea(&handler);
uiGridAppend(grid, uiControl(area),
1, 1, 1, 1,
1, uiAlignFill, 1, uiAlignFill);
// and set up the initial draw
remakeAttrStr();
uiControlShow(uiControl(mainwin));
uiMain();
uiControlDestroy(uiControl(mainwin));
uiFreeAttributedString(attrstr);
uiUninit();
return 0;
}

View File

@ -1,12 +1,18 @@
# 3 june 2016
list(APPEND _LIBUI_SOURCES
common/attribute.c
common/attrlist.c
common/attrstr.c
common/areaevents.c
common/control.c
common/debug.c
# common/drawtext.c
common/matrix.c
common/opentype.c
common/shouldquit.c
common/userbugs.c
common/utf.c
)
set(_LIBUI_SOURCES ${_LIBUI_SOURCES} PARENT_SCOPE)

View File

@ -10,11 +10,13 @@ For GTK+, we pull the double-click time and double-click distance, which work th
On GTK+ this will also allow us to discard the GDK_BUTTON_2PRESS and GDK_BUTTON_3PRESS events, so the button press stream will be just like on other platforms.
Thanks to mclasen, garnacho_, halfline, and tristan in irc.gimp.net/#gtk+.
TODO note the bits about asymmetry and g_rcClick initial value not mattering in the oldnewthing article
*/
// x, y, xdist, ydist, and c.rect must have the same units
// so must time, maxTime, and c.prevTime
int clickCounterClick(clickCounter *c, int button, int x, int y, uintptr_t time, uintptr_t maxTime, int32_t xdist, int32_t ydist)
int uiprivClickCounterClick(uiprivClickCounter *c, int button, int x, int y, uintptr_t time, uintptr_t maxTime, int32_t xdist, int32_t ydist)
{
// different button than before? if so, don't count
if (button != c->curButton)
@ -48,7 +50,7 @@ int clickCounterClick(clickCounter *c, int button, int x, int y, uintptr_t time,
return c->count;
}
void clickCounterReset(clickCounter *c)
void uiprivClickCounterReset(uiprivClickCounter *c)
{
c->curButton = 0;
c->rectX0 = 0;
@ -149,7 +151,7 @@ static const struct {
{ 0xFFFF, 0 },
};
int fromScancode(uintptr_t scancode, uiAreaKeyEvent *ke)
int uiprivFromScancode(uintptr_t scancode, uiAreaKeyEvent *ke)
{
int i;

266
common/attribute.c Normal file
View File

@ -0,0 +1,266 @@
// 19 february 2018
#include "../ui.h"
#include "uipriv.h"
#include "attrstr.h"
struct uiAttribute {
int ownedByUser;
size_t refcount;
uiAttributeType type;
union {
char *family;
double size;
uiTextWeight weight;
uiTextItalic italic;
uiTextStretch stretch;
struct {
double r;
double g;
double b;
double a;
// put this here so we can reuse this structure
uiUnderlineColor underlineColor;
} color;
uiUnderline underline;
uiOpenTypeFeatures *features;
} u;
};
static uiAttribute *newAttribute(uiAttributeType type)
{
uiAttribute *a;
a = uiprivNew(uiAttribute);
a->ownedByUser = 1;
a->refcount = 0;
a->type = type;
return a;
}
// returns a to allow expressions like b = uiprivAttributeRetain(a)
// TODO would this allow us to copy attributes between strings in a foreach func, and if so, should that be allowed?
uiAttribute *uiprivAttributeRetain(uiAttribute *a)
{
a->ownedByUser = 0;
a->refcount++;
return a;
}
static void destroy(uiAttribute *a)
{
switch (a->type) {
case uiAttributeTypeFamily:
uiprivFree(a->u.family);
break;
case uiAttributeTypeFeatures:
uiFreeOpenTypeFeatures(a->u.features);
break;
}
uiprivFree(a);
}
void uiprivAttributeRelease(uiAttribute *a)
{
if (a->ownedByUser)
/* TODO implementation bug: we can't release an attribute we don't own */;
a->refcount--;
if (a->refcount == 0)
destroy(a);
}
void uiFreeAttribute(uiAttribute *a)
{
if (!a->ownedByUser)
/* TODO user bug: you can't free an attribute you don't own */;
destroy(a);
}
uiAttributeType uiAttributeGetType(const uiAttribute *a)
{
return a->type;
}
uiAttribute *uiNewFamilyAttribute(const char *family)
{
uiAttribute *a;
a = newAttribute(uiAttributeTypeFamily);
a->u.family = (char *) uiprivAlloc((strlen(family) + 1) * sizeof (char), "char[] (uiAttribute)");
strcpy(a->u.family, family);
return a;
}
const char *uiAttributeFamily(const uiAttribute *a)
{
return a->u.family;
}
uiAttribute *uiNewSizeAttribute(double size)
{
uiAttribute *a;
a = newAttribute(uiAttributeTypeSize);
a->u.size = size;
return a;
}
double uiAttributeSize(const uiAttribute *a)
{
return a->u.size;
}
uiAttribute *uiNewWeightAttribute(uiTextWeight weight)
{
uiAttribute *a;
a = newAttribute(uiAttributeTypeWeight);
a->u.weight = weight;
return a;
}
uiTextWeight uiAttributeWeight(const uiAttribute *a)
{
return a->u.weight;
}
uiAttribute *uiNewItalicAttribute(uiTextItalic italic)
{
uiAttribute *a;
a = newAttribute(uiAttributeTypeItalic);
a->u.italic = italic;
return a;
}
uiTextItalic uiAttributeItalic(const uiAttribute *a)
{
return a->u.italic;
}
uiAttribute *uiNewStretchAttribute(uiTextStretch stretch)
{
uiAttribute *a;
a = newAttribute(uiAttributeTypeStretch);
a->u.stretch = stretch;
return a;
}
uiTextStretch uiAttributeStretch(const uiAttribute *a)
{
return a->u.stretch;
}
uiAttribute *uiNewColorAttribute(double r, double g, double b, double a)
{
uiAttribute *at;
at = newAttribute(uiAttributeTypeColor);
at->u.color.r = r;
at->u.color.g = g;
at->u.color.b = b;
at->u.color.a = a;
return at;
}
void uiAttributeColor(const uiAttribute *a, double *r, double *g, double *b, double *alpha)
{
*r = a->u.color.r;
*g = a->u.color.g;
*b = a->u.color.b;
*alpha = a->u.color.a;
}
uiAttribute *uiNewBackgroundAttribute(double r, double g, double b, double a)
{
uiAttribute *at;
at = newAttribute(uiAttributeTypeBackground);
at->u.color.r = r;
at->u.color.g = g;
at->u.color.b = b;
at->u.color.a = a;
return at;
}
uiAttribute *uiNewUnderlineAttribute(uiUnderline u)
{
uiAttribute *a;
a = newAttribute(uiAttributeTypeUnderline);
a->u.underline = u;
return a;
}
uiUnderline uiAttributeUnderline(const uiAttribute *a)
{
return a->u.underline;
}
uiAttribute *uiNewUnderlineColorAttribute(uiUnderlineColor u, double r, double g, double b, double a)
{
uiAttribute *at;
at = uiNewColorAttribute(r, g, b, a);
at->type = uiAttributeTypeUnderlineColor;
at->u.color.underlineColor = u;
return at;
}
void uiAttributeUnderlineColor(const uiAttribute *a, uiUnderlineColor *u, double *r, double *g, double *b, double *alpha)
{
*u = a->u.color.underlineColor;
uiAttributeColor(a, r, g, b, alpha);
}
uiAttribute *uiNewFeaturesAttribute(const uiOpenTypeFeatures *otf)
{
uiAttribute *a;
a = newAttribute(uiAttributeTypeFeatures);
a->u.features = uiOpenTypeFeaturesClone(otf);
return a;
}
const uiOpenTypeFeatures *uiAttributeFeatures(const uiAttribute *a)
{
return a->u.features;
}
int uiprivAttributeEqual(const uiAttribute *a, const uiAttribute *b)
{
if (a == b)
return 1;
if (a->type != b->type)
return 0;
switch (a->type) {
case uiAttributeTypeFamily:
return uiprivStricmp(a->u.family, b->u.family);
case uiAttributeTypeSize:
// TODO is the use of == correct?
return a->u.size == b->u.size;
case uiAttributeTypeWeight:
return a->u.weight == b->u.weight;
case uiAttributeTypeItalic:
return a->u.italic == b->u.italic;
case uiAttributeTypeStretch:
return a->u.stretch == b->u.stretch;
case uiAttributeTypeUnderline:
return a->u.underline == b->u.underline;
case uiAttributeTypeUnderlineColor:
if (a->u.color.underlineColor != b->u.color.underlineColor)
return 0;
// fall through
case uiAttributeTypeColor:
case uiAttributeTypeBackground:
// TODO is the use of == correct?
return (a->u.color.r == b->u.color.r) &&
(a->u.color.g == b->u.color.g) &&
(a->u.color.b == b->u.color.b) &&
(a->u.color.a == b->u.color.a);
case uiAttributeTypeFeatures:
return uiprivOpenTypeFeaturesEqual(a->u.features, b->u.features);
}
// TODO should not be reached
return 0;
}

612
common/attrlist.c Normal file
View File

@ -0,0 +1,612 @@
// 16 december 2016
#include "../ui.h"
#include "uipriv.h"
#include "attrstr.h"
/*
An attribute list is a doubly linked list of attributes.
Attribute start positions are inclusive and attribute end positions are exclusive (or in other words, [start, end)).
The list is kept sorted in increasing order by start position. Whether or not the sort is stable is undefined, so no temporal information should be expected to stay.
Overlapping attributes are not allowed; if an attribute is added that conflicts with an existing one, the existing one is removed.
In addition, the linked list tries to reduce fragmentation: if an attribute is added that just expands another, then there will only be one entry in alist, not two. (TODO does it really?)
The linked list is not a ring; alist->fist->prev == NULL and alist->last->next == NULL.
TODO verify that this disallows attributes of length zero
*/
struct attr {
uiAttribute *val;
size_t start;
size_t end;
struct attr *prev;
struct attr *next;
};
struct uiprivAttrList {
struct attr *first;
struct attr *last;
};
// if before is NULL, add to the end of the list
static void attrInsertBefore(uiprivAttrList *alist, struct attr *a, struct attr *before)
{
// if the list is empty, this is the first item
if (alist->first == NULL) {
alist->first = a;
alist->last = a;
return;
}
// add to the end
if (before == NULL) {
struct attr *oldlast;
oldlast = alist->last;
alist->last = a;
a->prev = oldlast;
oldlast->next = a;
return;
}
// add to the beginning
if (before == alist->first) {
struct attr *oldfirst;
oldfirst = alist->first;
alist->first = a;
oldfirst->prev = a;
a->next = oldfirst;
return;
}
// add to the middle
a->prev = before->prev;
a->next = before;
before->prev = a;
a->prev->next = a;
}
static int attrHasPos(struct attr *a, size_t pos)
{
if (pos < a->start)
return 0;
return pos < a->end;
}
// returns 1 if there was an intersection and 0 otherwise
static int attrRangeIntersect(struct attr *a, size_t *start, size_t *end)
{
// is the range outside a entirely?
if (*start >= a->end)
return 0;
if (*end < a->start)
return 0;
// okay, so there is an overlap
// compute the intersection
if (*start < a->start)
*start = a->start;
if (*end > a->end)
*end = a->end;
return 1;
}
// returns the old a->next, for forward iteration
static struct attr *attrUnlink(uiprivAttrList *alist, struct attr *a)
{
struct attr *p, *n;
p = a->prev;
n = a->next;
a->prev = NULL;
a->next = NULL;
// only item in list?
if (p == NULL && n == NULL) {
alist->first = NULL;
alist->last = NULL;
return NULL;
}
// start of list?
if (p == NULL) {
n->prev = NULL;
alist->first = n;
return n;
}
// end of list?
if (n == NULL) {
p->next = NULL;
alist->last = p;
return NULL;
}
// middle of list
p->next = n;
n->prev = p;
return n;
}
// returns the old a->next, for forward iteration
static struct attr *attrDelete(uiprivAttrList *alist, struct attr *a)
{
struct attr *next;
next = attrUnlink(alist, a);
uiprivAttributeRelease(a->val);
uiprivFree(a);
return next;
}
// attrDropRange() removes attributes without deleting characters.
//
// If the attribute needs no change, then nothing is done.
//
// If the attribute needs to be deleted, it is deleted.
//
// If the attribute only needs to be resized at the end, it is adjusted.
//
// If the attribute only needs to be resized at the start, it is adjusted, unlinked, and returned in tail.
//
// Otherwise, the attribute needs to be split. The existing attribute is adjusted to make the left half and a new attribute with the right half. This attribute is kept unlinked and returned in tail.
//
// In all cases, the return value is the next attribute to look at in a forward sequential loop.
static struct attr *attrDropRange(uiprivAttrList *alist, struct attr *a, size_t start, size_t end, struct attr **tail)
{
struct attr *b;
// always pre-initialize tail to NULL
*tail = NULL;
if (!attrRangeIntersect(a, &start, &end))
// out of range; nothing to do
return a->next;
// just outright delete the attribute?
// the inequalities handle attributes entirely inside the range
// if both are equal, the attribute's range is equal to the range
if (a->start >= start && a->end <= end)
return attrDelete(alist, a);
// only chop off the start or end?
if (a->start == start) { // chop off the start
// we are dropping the left half, so set a->start and unlink
a->start = end;
*tail = a;
return attrUnlink(alist, a);
}
if (a->end == end) { // chop off the end
// we are dropping the right half, so just set a->end
a->end = start;
return a->next;
}
// we'll need to split the attribute into two
b = uiprivNew(struct attr);
b->val = uiprivAttributeRetain(a->val);
b->start = end;
b->end = a->end;
*tail = b;
a->end = start;
return a->next;
}
static void attrGrow(uiprivAttrList *alist, struct attr *a, size_t start, size_t end)
{
struct attr *before;
// adjusting the end is simple: if it ends before our new end, just set the new end
if (a->end < end)
a->end = end;
// adjusting the start is harder
// if the start is before our new start, we are done
// otherwise, we have to move the start back AND reposition the attribute to keep the sorted order
if (a->start <= start)
return;
a->start = start;
attrUnlink(alist, a);
for (before = alist->first; before != NULL; before = before->next)
if (before->start > a->start)
break;
attrInsertBefore(alist, a, before);
}
// returns the right side of the split, which is unlinked, or NULL if no split was done
static struct attr *attrSplitAt(uiprivAttrList *alist, struct attr *a, size_t at)
{
struct attr *b;
// no splittng needed?
// note the equality: we don't need to split at start or end
// in the end case, the last split point is at - 1; at itself is outside the range, and at - 1 results in the right hand side having length 1
if (at <= a->start)
return NULL;
if (at >= a->end)
return NULL;
b = uiprivNew(struct attr);
b->val = uiprivAttributeRetain(a->val);
b->start = at;
b->end = a->end;
a->end = at;
return b;
}
// attrDeleteRange() removes attributes while deleting characters.
//
// If the attribute does not include the deleted range, then nothing is done (though the start and end are adjusted as necessary).
//
// If the attribute needs to be deleted, it is deleted.
//
// Otherwise, the attribute only needs the start or end deleted, and it is adjusted.
//
// In all cases, the return value is the next attribute to look at in a forward sequential loop.
// TODO rewrite this comment
static struct attr *attrDeleteRange(uiprivAttrList *alist, struct attr *a, size_t start, size_t end)
{
size_t ostart, oend;
size_t count;
ostart = start;
oend = end;
count = oend - ostart;
if (!attrRangeIntersect(a, &start, &end)) {
// out of range
// adjust if necessary
if (a->start >= ostart)
a->start -= count;
if (a->end >= oend)
a->end -= count;
return a->next;
}
// just outright delete the attribute?
// the inequalities handle attributes entirely inside the range
// if both are equal, the attribute's range is equal to the range
if (a->start >= start && a->end <= end)
return attrDelete(alist, a);
// only chop off the start or end?
if (a->start == start) { // chop off the start
// if we weren't adjusting positions this would just be setting a->start to end
// but since this is deleting from the start, we need to adjust both by count
a->start = end - count;
a->end -= count;
return a->next;
}
if (a->end == end) { // chop off the end
// a->start is already good
a->end = start;
return a->next;
}
// in this case, the deleted range is inside the attribute
// we can clear it by just removing count from a->end
a->end -= count;
return a->next;
}
uiprivAttrList *uiprivNewAttrList(void)
{
return uiprivNew(uiprivAttrList);
}
void uiprivFreeAttrList(uiprivAttrList *alist)
{
struct attr *a, *next;
a = alist->first;
while (a != NULL) {
next = a->next;
uiprivAttributeRelease(a->val);
uiprivFree(a);
a = next;
}
uiprivFree(alist);
}
void uiprivAttrListInsertAttribute(uiprivAttrList *alist, uiAttribute *val, size_t start, size_t end)
{
struct attr *a;
struct attr *before;
struct attr *tail = NULL;
int split = 0;
uiAttributeType valtype;
// first, figure out where in the list this should go
// in addition, if this attribute overrides one that already exists, split that one apart so this one can take over
before = alist->first;
valtype = uiAttributeGetType(val);
while (before != NULL) {
size_t lstart, lend;
// once we get to the first point after start, we know where to insert
if (before->start > start)
break;
// if we have already split a prior instance of this attribute, don't bother doing it again
if (split)
goto next;
// should we split this attribute?
if (uiAttributeGetType(before->val) != valtype)
goto next;
lstart = start;
lend = end;
if (!attrRangeIntersect(before, &lstart, &lend))
goto next;
// okay so this might conflict; if the val is the same as the one we want, we need to expand the existing attribute, not fragment anything
// TODO will this reduce fragmentation if we first add from 0 to 2 and then from 2 to 4? or do we have to do that separately?
if (uiprivAttributeEqual(before->val, val)) {
attrGrow(alist, before, start, end);
return;
}
// okay the values are different; we need to split apart
before = attrDropRange(alist, before, start, end, &tail);
split = 1;
continue;
next:
before = before->next;
}
// if we got here, we know we have to add the attribute before before
a = uiprivNew(struct attr);
a->val = uiprivAttributeRetain(val);
a->start = start;
a->end = end;
attrInsertBefore(alist, a, before);
// and finally, if we split, insert the remainder
if (tail == NULL)
return;
// note we start at before; it won't be inserted before that by the sheer nature of how the code above works
for (; before != NULL; before = before->next)
if (before->start > tail->start)
break;
attrInsertBefore(alist, tail, before);
}
void uiprivAttrListInsertCharactersUnattributed(uiprivAttrList *alist, size_t start, size_t count)
{
struct attr *a;
struct attr *tails = NULL;
// every attribute before the insertion point can either cross into the insertion point or not
// if it does, we need to split that attribute apart at the insertion point, keeping only the old attribute in place, adjusting the new tail, and preparing it for being re-added later
for (a = alist->first; a != NULL; a = a->next) {
struct attr *tail;
// stop once we get to the insertion point
if (a->start >= start)
break;
// only do something if overlapping
if (!attrHasPos(a, start))
continue;
tail = attrSplitAt(alist, a, start);
// adjust the new tail for the insertion
tail->start += count;
tail->end += count;
// and queue it for re-adding later
// we can safely use tails as if it was singly-linked since it's just a temporary list; we properly merge them back in below and they'll be doubly-linked again then
// TODO actually we could probably save some time by making then doubly-linked now and adding them in one fell swoop, but that would make things a bit more complicated...
tail->next = tails;
tails = tail;
}
// at this point in the attribute list, we are at or after the insertion point
// all the split-apart attributes will be at the insertion point
// therefore, we can just add them all back now, and the list will still be sorted correctly
while (tails != NULL) {
struct attr *next;
// make all the links NULL before insertion, just to be safe
next = tails->next;
tails->next = NULL;
attrInsertBefore(alist, tails, a);
tails = next;
}
// every remaining attribute will be either at or after the insertion point
// we just need to move them ahead
for (; a != NULL; a = a->next) {
a->start += count;
a->end += count;
}
}
// The attributes are those of character start - 1.
// If start == 0, the attributes are those of character 0.
/*
This is an obtuse function. Here's some diagrams to help.
Given the input string
abcdefghi (positions: 012345678 9)
and attribute set
red start 0 end 3
bold start 2 end 6
underline start 5 end 8
or visually:
012345678 9
rrr------
--bbbb---
-----uuu-
If we insert qwe to result in positions 0123456789AB C:
before 0, 1, 2 (grow the red part, move everything else down)
red -> start 0 (no change) end 3+3=6
bold -> start 2+3=5 end 6+3=9
underline -> start 5+3=8 end 8+3=B
before 3 (grow red and bold, move underline down)
red -> start 0 (no change) end 3+3=6
bold -> start 2 (no change) end 6+3=9
underline -> start 5+3=8 end 8+3=B
before 4, 5 (keep red, grow bold, move underline down)
red -> start 0 (no change) end 3 (no change)
bold -> start 2 (no change) end 6+3=9
underline -> start 5+3=8 end 8+3=B
before 6 (keep red, grow bold and underline)
red -> start 0 (no change) end 3 (no change)
bold -> start 2 (no change) end 6+3=9
underline -> start 5 (no change) end 8+3=B
before 7, 8 (keep red and bold, grow underline)
red -> start 0 (no change) end 3 (no change)
bold -> start 2 (no change) end 6 (no change)
underline -> start 5 (no change) end 8+3=B
before 9 (keep all three)
red -> start 0 (no change) end 3 (no change)
bold -> start 2 (no change) end 6 (no change)
underline -> start 5 (no change) end 8 (no change)
result:
0 1 2 3 4 5 6 7 8 9
red E E E e n n n n n n
bold s s S E E E e n n n
underline s s s s s S E E e n
N = none
E = end only
S = start and end
uppercase = in original range, lowercase = not
which results in our algorithm:
for each attribute
if start < insertion point
move start up
else if start == insertion point
if start != 0
move start up
if end <= insertion point
move end up
*/
// TODO does this ensure the list remains sorted?
void uiprivAttrListInsertCharactersExtendingAttributes(uiprivAttrList *alist, size_t start, size_t count)
{
struct attr *a;
for (a = alist->first; a != NULL; a = a->next) {
if (a->start < start)
a->start += count;
else if (a->start == start && start != 0)
a->start += count;
if (a->end <= start)
a->end += count;
}
}
// TODO replace at point with — replaces with first character's attributes
void uiprivAttrListRemoveAttribute(uiprivAttrList *alist, uiAttributeType type, size_t start, size_t end)
{
struct attr *a;
struct attr *tails = NULL; // see uiprivAttrListInsertCharactersUnattributed() above
struct attr *tailsAt = NULL;
a = alist->first;
while (a != NULL) {
size_t lstart, lend;
struct attr *tail;
// this defines where to re-attach the tails
// (all the tails will have their start at end, so we can just insert them all before tailsAt)
if (a->start >= end) {
tailsAt = a;
// and at this point we're done, so
break;
}
if (uiAttributeGetType(a->val) != type)
goto next;
lstart = start;
lend = end;
if (!attrRangeIntersect(a, &lstart, &lend))
goto next;
a = attrDropRange(alist, a, start, end, &tail);
if (tail != NULL) {
tail->next = tails;
tails = tail;
}
continue;
next:
a = a->next;
}
while (tails != NULL) {
struct attr *next;
// make all the links NULL before insertion, just to be safe
next = tails->next;
tails->next = NULL;
attrInsertBefore(alist, tails, a);
tails = next;
}
}
// TODO merge this with the above
void uiprivAttrListRemoveAttributes(uiprivAttrList *alist, size_t start, size_t end)
{
struct attr *a;
struct attr *tails = NULL; // see uiprivAttrListInsertCharactersUnattributed() above
struct attr *tailsAt = NULL;
a = alist->first;
while (a != NULL) {
size_t lstart, lend;
struct attr *tail;
// this defines where to re-attach the tails
// (all the tails will have their start at end, so we can just insert them all before tailsAt)
if (a->start >= end) {
tailsAt = a;
// and at this point we're done, so
break;
}
lstart = start;
lend = end;
if (!attrRangeIntersect(a, &lstart, &lend))
goto next;
a = attrDropRange(alist, a, start, end, &tail);
if (tail != NULL) {
tail->next = tails;
tails = tail;
}
continue;
next:
a = a->next;
}
while (tails != NULL) {
struct attr *next;
// make all the links NULL before insertion, just to be safe
next = tails->next;
tails->next = NULL;
attrInsertBefore(alist, tails, a);
tails = next;
}
}
void uiprivAttrListRemoveCharacters(uiprivAttrList *alist, size_t start, size_t end)
{
struct attr *a;
a = alist->first;
while (a != NULL)
a = attrDeleteRange(alist, a, start, end);
}
void uiprivAttrListForEach(const uiprivAttrList *alist, const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data)
{
struct attr *a;
uiForEach ret;
for (a = alist->first; a != NULL; a = a->next) {
ret = (*f)(s, a->val, a->start, a->end, data);
if (ret == uiForEachStop)
// TODO for all: break or return?
break;
}
}

357
common/attrstr.c Normal file
View File

@ -0,0 +1,357 @@
// 3 december 2016
#include "../ui.h"
#include "uipriv.h"
#include "attrstr.h"
struct uiAttributedString {
char *s;
size_t len;
uiprivAttrList *attrs;
// indiscriminately keep a UTF-16 copy of the string on all platforms so we can hand this off to the grapheme calculator
// this ensures no one platform has a speed advantage (sorry GTK+)
uint16_t *u16;
size_t u16len;
size_t *u8tou16;
size_t *u16tou8;
// this is lazily created to keep things from getting *too* slow
uiprivGraphemes *graphemes;
};
static void resize(uiAttributedString *s, size_t u8, size_t u16)
{
s->len = u8;
s->s = (char *) uiprivRealloc(s->s, (s->len + 1) * sizeof (char), "char[] (uiAttributedString)");
s->u8tou16 = (size_t *) uiprivRealloc(s->u8tou16, (s->len + 1) * sizeof (size_t), "size_t[] (uiAttributedString)");
s->u16len = u16;
s->u16 = (uint16_t *) uiprivRealloc(s->u16, (s->u16len + 1) * sizeof (uint16_t), "uint16_t[] (uiAttributedString)");
s->u16tou8 = (size_t *) uiprivRealloc(s->u16tou8, (s->u16len + 1) * sizeof (size_t), "size_t[] (uiAttributedString)");
}
uiAttributedString *uiNewAttributedString(const char *initialString)
{
uiAttributedString *s;
s = uiprivNew(uiAttributedString);
s->attrs = uiprivNewAttrList();
uiAttributedStringAppendUnattributed(s, initialString);
return s;
}
// TODO make sure that all implementations of uiprivNewGraphemes() work fine with empty strings; in particular, the Windows one might not
static void recomputeGraphemes(uiAttributedString *s)
{
if (s->graphemes != NULL)
return;
if (uiprivGraphemesTakesUTF16()) {
s->graphemes = uiprivNewGraphemes(s->u16, s->u16len);
return;
}
s->graphemes = uiprivNewGraphemes(s->s, s->len);
}
static void invalidateGraphemes(uiAttributedString *s)
{
if (s->graphemes == NULL)
return;
uiprivFree(s->graphemes->pointsToGraphemes);
uiprivFree(s->graphemes->graphemesToPoints);
uiprivFree(s->graphemes);
s->graphemes = NULL;
}
void uiFreeAttributedString(uiAttributedString *s)
{
uiprivFreeAttrList(s->attrs);
invalidateGraphemes(s);
uiprivFree(s->u16tou8);
uiprivFree(s->u8tou16);
uiprivFree(s->u16);
uiprivFree(s->s);
uiprivFree(s);
}
const char *uiAttributedStringString(const uiAttributedString *s)
{
return s->s;
}
size_t uiAttributedStringLen(const uiAttributedString *s)
{
return s->len;
}
static void u8u16len(const char *str, size_t *n8, size_t *n16)
{
uint32_t rune;
char buf[4];
uint16_t buf16[2];
*n8 = 0;
*n16 = 0;
while (*str) {
str = uiprivUTF8DecodeRune(str, 0, &rune);
// TODO document the use of the function vs a pointer subtract here
// TODO also we need to consider namespace collision with utf.h...
*n8 += uiprivUTF8EncodeRune(rune, buf);
*n16 += uiprivUTF16EncodeRune(rune, buf16);
}
}
void uiAttributedStringAppendUnattributed(uiAttributedString *s, const char *str)
{
uiAttributedStringInsertAtUnattributed(s, str, s->len);
}
// this works (and returns true, which is what we want) at s->len too because s->s[s->len] is always going to be 0 due to us allocating s->len + 1 bytes and because uiprivRealloc() always zero-fills allocated memory
static int onCodepointBoundary(uiAttributedString *s, size_t at)
{
uint8_t c;
// for uiNewAttributedString()
if (s->s == NULL && at == 0)
return 1;
c = (uint8_t) (s->s[at]);
return c < 0x80 || c >= 0xC0;
}
// TODO note that at must be on a codeoint boundary
void uiAttributedStringInsertAtUnattributed(uiAttributedString *s, const char *str, size_t at)
{
uint32_t rune;
char buf[4];
uint16_t buf16[2];
size_t n8, n16; // TODO make loop-local? to avoid using them in the wrong place again
size_t old, old16;
size_t oldn8, oldn16;
size_t oldlen, old16len;
size_t at16;
size_t i;
if (!onCodepointBoundary(s, at)) {
// TODO
}
at16 = 0;
if (s->u8tou16 != NULL)
at16 = s->u8tou16[at];
// do this first to reclaim memory
invalidateGraphemes(s);
// first figure out how much we need to grow by
// this includes post-validated UTF-8
u8u16len(str, &n8, &n16);
// and resize
old = at;
old16 = at16;
oldlen = s->len;
old16len = s->u16len;
resize(s, s->len + n8, s->u16len + n16);
// move existing characters out of the way
// note the use of memmove(): https://twitter.com/rob_pike/status/737797688217894912
memmove(
s->s + at + n8,
s->s + at,
(oldlen - at) * sizeof (char));
memmove(
s->u16 + at16 + n16,
s->u16 + at16,
(old16len - at16) * sizeof (uint16_t));
// note the + 1 for these; we want to copy the terminating null too
memmove(
s->u8tou16 + at + n8,
s->u8tou16 + at,
(oldlen - at + 1) * sizeof (size_t));
memmove(
s->u16tou8 + at16 + n16,
s->u16tou8 + at16,
(old16len - at16 + 1) * sizeof (size_t));
oldn8 = n8;
oldn16 = n16;
// and copy
while (*str) {
size_t n;
str = uiprivUTF8DecodeRune(str, 0, &rune);
n = uiprivUTF8EncodeRune(rune, buf);
n16 = uiprivUTF16EncodeRune(rune, buf16);
s->s[old] = buf[0];
s->u8tou16[old] = old16;
if (n > 1) {
s->s[old + 1] = buf[1];
s->u8tou16[old + 1] = old16;
}
if (n > 2) {
s->s[old + 2] = buf[2];
s->u8tou16[old + 2] = old16;
}
if (n > 3) {
s->s[old + 3] = buf[3];
s->u8tou16[old + 3] = old16;
}
s->u16[old16] = buf16[0];
s->u16tou8[old16] = old;
if (n16 > 1) {
s->u16[old16 + 1] = buf16[1];
s->u16tou8[old16 + 1] = old;
}
old += n;
old16 += n16;
}
// and have an index for the end of the string
// TODO is this done by the below?
//TODO s->u8tou16[old] = old16;
//TODO s->u16tou8[old16] = old;
// and adjust the prior values in the conversion tables
// use <= so the terminating 0 gets updated too
for (i = 0; i <= oldlen - at; i++)
s->u8tou16[at + oldn8 + i] += s->u16len - old16len;
for (i = 0; i <= old16len - at16; i++)
s->u16tou8[at16 + oldn16 + i] += s->len - oldlen;
// and finally do the attributes
uiprivAttrListInsertCharactersUnattributed(s->attrs, at, n8);
}
// TODO document that end is the first index that will be maintained
void uiAttributedStringDelete(uiAttributedString *s, size_t start, size_t end)
{
size_t start16, end16;
size_t count, count16;
size_t i;
if (!onCodepointBoundary(s, start)) {
// TODO
}
if (!onCodepointBoundary(s, end)) {
// TODO
}
count = end - start;
start16 = s->u8tou16[start];
end16 = s->u8tou16[end];
count16 = end16 - start16;
invalidateGraphemes(s);
// overwrite old characters
memmove(
s->s + start,
s->s + end,
(s->len - end) * sizeof (char));
memmove(
s->u16 + start16,
s->u16 + end16,
(s->u16len - end16) * sizeof (uint16_t));
// note the + 1 for these; we want to copy the terminating null too
memmove(
s->u8tou16 + start,
s->u8tou16 + end,
(s->len - end + 1) * sizeof (size_t));
memmove(
s->u16tou8 + start16,
s->u16tou8 + end16,
(s->u16len - end16 + 1) * sizeof (size_t));
// update the conversion tables
// note the use of <= to include the null terminator
for (i = 0; i <= count; i++)
s->u8tou16[start + i] -= count16;
for (i = 0; i <= count16; i++)
s->u16tou8[start16 + i] -= count;
// null-terminate the string
s->s[start + count] = 0;
s->u16[start16 + count16] = 0;
// fix up attributes
uiprivAttrListRemoveCharacters(s->attrs, start, end);
// and finally resize
resize(s, start + count, start16 + count16);
}
void uiAttributedStringSetAttribute(uiAttributedString *s, uiAttribute *a, size_t start, size_t end)
{
uiprivAttrListInsertAttribute(s->attrs, a, start, end);
}
// LONGTERM introduce an iterator object instead?
void uiAttributedStringForEachAttribute(const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data)
{
uiprivAttrListForEach(s->attrs, s, f, data);
}
// TODO figure out if we should count the grapheme past the end
size_t uiAttributedStringNumGraphemes(uiAttributedString *s)
{
recomputeGraphemes(s);
return s->graphemes->len;
}
size_t uiAttributedStringByteIndexToGrapheme(uiAttributedString *s, size_t pos)
{
recomputeGraphemes(s);
if (uiprivGraphemesTakesUTF16())
pos = s->u8tou16[pos];
return s->graphemes->pointsToGraphemes[pos];
}
size_t uiAttributedStringGraphemeToByteIndex(uiAttributedString *s, size_t pos)
{
recomputeGraphemes(s);
pos = s->graphemes->graphemesToPoints[pos];
if (uiprivGraphemesTakesUTF16())
pos = s->u16tou8[pos];
return pos;
}
// helpers for platform-specific code
const uint16_t *uiprivAttributedStringUTF16String(const uiAttributedString *s)
{
return s->u16;
}
size_t uiprivAttributedStringUTF16Len(const uiAttributedString *s)
{
return s->u16len;
}
// TODO is this still needed given the below?
size_t uiprivAttributedStringUTF8ToUTF16(const uiAttributedString *s, size_t n)
{
return s->u8tou16[n];
}
size_t *uiprivAttributedStringCopyUTF8ToUTF16Table(const uiAttributedString *s, size_t *n)
{
size_t *out;
size_t nbytes;
nbytes = (s->len + 1) * sizeof (size_t);
*n = s->len;
out = (size_t *) uiprivAlloc(nbytes, "size_t[] (uiAttributedString)");
memmove(out, s->u8tou16, nbytes);
return out;
}
size_t *uiprivAttributedStringCopyUTF16ToUTF8Table(const uiAttributedString *s, size_t *n)
{
size_t *out;
size_t nbytes;
nbytes = (s->u16len + 1) * sizeof (size_t);
*n = s->u16len;
out = (size_t *) uiprivAlloc(nbytes, "size_t[] (uiAttributedString)");
memmove(out, s->u16tou8, nbytes);
return out;
}

46
common/attrstr.h Normal file
View File

@ -0,0 +1,46 @@
// 19 february 2018
#ifdef __cplusplus
extern "C" {
#endif
// attribute.c
extern uiAttribute *uiprivAttributeRetain(uiAttribute *a);
extern void uiprivAttributeRelease(uiAttribute *a);
extern int uiprivAttributeEqual(const uiAttribute *a, const uiAttribute *b);
// opentype.c
extern int uiprivOpenTypeFeaturesEqual(const uiOpenTypeFeatures *a, const uiOpenTypeFeatures *b);
// attrlist.c
typedef struct uiprivAttrList uiprivAttrList;
extern uiprivAttrList *uiprivNewAttrList(void);
extern void uiprivFreeAttrList(uiprivAttrList *alist);
extern void uiprivAttrListInsertAttribute(uiprivAttrList *alist, uiAttribute *val, size_t start, size_t end);
extern void uiprivAttrListInsertCharactersUnattributed(uiprivAttrList *alist, size_t start, size_t count);
extern void uiprivAttrListInsertCharactersExtendingAttributes(uiprivAttrList *alist, size_t start, size_t count);
extern void uiprivAttrListRemoveAttribute(uiprivAttrList *alist, uiAttributeType type, size_t start, size_t end);
extern void uiprivAttrListRemoveAttributes(uiprivAttrList *alist, size_t start, size_t end);
extern void uiprivAttrListRemoveCharacters(uiprivAttrList *alist, size_t start, size_t end);
extern void uiprivAttrListForEach(const uiprivAttrList *alist, const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data);
// attrstr.c
extern const uint16_t *uiprivAttributedStringUTF16String(const uiAttributedString *s);
extern size_t uiprivAttributedStringUTF16Len(const uiAttributedString *s);
extern size_t uiprivAttributedStringUTF8ToUTF16(const uiAttributedString *s, size_t n);
extern size_t *uiprivAttributedStringCopyUTF8ToUTF16Table(const uiAttributedString *s, size_t *n);
extern size_t *uiprivAttributedStringCopyUTF16ToUTF8Table(const uiAttributedString *s, size_t *n);
// per-OS graphemes.c/graphemes.cpp/graphemes.m/etc.
typedef struct uiprivGraphemes uiprivGraphemes;
struct uiprivGraphemes {
size_t len;
size_t *pointsToGraphemes;
size_t *graphemesToPoints;
};
extern int uiprivGraphemesTakesUTF16(void);
extern uiprivGraphemes *uiprivNewGraphemes(void *s, size_t len);
#ifdef __cplusplus
}
#endif

View File

@ -57,14 +57,14 @@ void uiControlDisable(uiControl *c)
(*(c->Disable))(c);
}
#define uiControlSignature 0x7569436F
#define uiprivControlSignature 0x7569436F
uiControl *uiAllocControl(size_t size, uint32_t OSsig, uint32_t typesig, const char *typenamestr)
{
uiControl *c;
c = (uiControl *) uiAlloc(size, typenamestr);
c->Signature = uiControlSignature;
c = (uiControl *) uiprivAlloc(size, typenamestr);
c->Signature = uiprivControlSignature;
c->OSSignature = OSsig;
c->TypeSignature = typesig;
return c;
@ -73,8 +73,8 @@ uiControl *uiAllocControl(size_t size, uint32_t OSsig, uint32_t typesig, const c
void uiFreeControl(uiControl *c)
{
if (uiControlParent(c) != NULL)
userbug("You cannot destroy a uiControl while it still has a parent. (control: %p)", c);
uiFree(c);
uiprivUserBug("You cannot destroy a uiControl while it still has a parent. (control: %p)", c);
uiprivFree(c);
}
void uiControlVerifySetParent(uiControl *c, uiControl *parent)
@ -82,12 +82,12 @@ void uiControlVerifySetParent(uiControl *c, uiControl *parent)
uiControl *curParent;
if (uiControlToplevel(c))
userbug("You cannot give a toplevel uiControl a parent. (control: %p)", c);
uiprivUserBug("You cannot give a toplevel uiControl a parent. (control: %p)", c);
curParent = uiControlParent(c);
if (parent != NULL && curParent != NULL)
userbug("You cannot give a uiControl a parent while it already has one. (control: %p; current parent: %p; new parent: %p)", c, curParent, parent);
uiprivUserBug("You cannot give a uiControl a parent while it already has one. (control: %p; current parent: %p; new parent: %p)", c, curParent, parent);
if (parent == NULL && curParent == NULL)
implbug("attempt to double unparent uiControl %p", c);
uiprivImplBug("attempt to double unparent uiControl %p", c);
}
int uiControlEnabledToUser(uiControl *c)

View File

@ -1,5 +1,7 @@
// 24 april 2016
// LONGTERM if I don't decide to remove these outright, should they be renamed uiprivTypeNameSignature? these aren't real symbols, so...
#define uiAreaSignature 0x41726561
#define uiBoxSignature 0x426F784C
#define uiButtonSignature 0x42746F6E

View File

@ -2,20 +2,20 @@
#include "../ui.h"
#include "uipriv.h"
void _implbug(const char *file, const char *line, const char *func, const char *format, ...)
void uiprivDoImplBug(const char *file, const char *line, const char *func, const char *format, ...)
{
va_list ap;
va_start(ap, format);
realbug(file, line, func, "POSSIBLE IMPLEMENTATION BUG; CONTACT ANDLABS:\n", format, ap);
uiprivRealBug(file, line, func, "POSSIBLE IMPLEMENTATION BUG; CONTACT ANDLABS:\n", format, ap);
va_end(ap);
}
void _userbug(const char *file, const char *line, const char *func, const char *format, ...)
void uiprivDoUserBug(const char *file, const char *line, const char *func, const char *format, ...)
{
va_list ap;
va_start(ap, format);
realbug(file, line, func, "You have a bug: ", format, ap);
uiprivRealBug(file, line, func, "You have a bug: ", format, ap);
va_end(ap);
}

View File

@ -18,7 +18,7 @@ void uiDrawMatrixSetIdentity(uiDrawMatrix *m)
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ff684171%28v=vs.85%29.aspx#skew_transform
// TODO see if there's a way we can avoid the multiplication
void fallbackSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
void uiprivFallbackSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
{
uiDrawMatrix n;
@ -31,7 +31,7 @@ void fallbackSkew(uiDrawMatrix *m, double x, double y, double xamount, double ya
uiDrawMatrixMultiply(m, &n);
}
void scaleCenter(double xCenter, double yCenter, double *x, double *y)
void uiprivScaleCenter(double xCenter, double yCenter, double *x, double *y)
{
*x = xCenter - (*x * xCenter);
*y = yCenter - (*y * yCenter);
@ -39,7 +39,7 @@ void scaleCenter(double xCenter, double yCenter, double *x, double *y)
// the basic algorithm is from cairo
// but it's the same algorithm as the transform point, just without M31 and M32 taken into account, so let's just do that instead
void fallbackTransformSize(uiDrawMatrix *m, double *x, double *y)
void uiprivFallbackTransformSize(uiDrawMatrix *m, double *x, double *y)
{
uiDrawMatrix m2;

165
common/opentype.c Normal file
View File

@ -0,0 +1,165 @@
// 25 february 2018
#include <stdlib.h>
#include "../ui.h"
#include "uipriv.h"
#include "attrstr.h"
struct feature {
char a;
char b;
char c;
char d;
uint32_t value;
};
struct uiOpenTypeFeatures {
struct feature *data;
size_t len;
size_t cap;
};
#define bytecount(n) ((n) * sizeof (struct feature))
uiOpenTypeFeatures *uiNewOpenTypeFeatures(void)
{
uiOpenTypeFeatures *otf;
otf = uiprivNew(uiOpenTypeFeatures);
otf->cap = 16;
otf->data = (struct feature *) uiprivAlloc(bytecount(otf->cap), "struct feature[]");
otf->len = 0;
return otf;
}
void uiFreeOpenTypeFeatures(uiOpenTypeFeatures *otf)
{
uiprivFree(otf->data);
uiprivFree(otf);
}
uiOpenTypeFeatures *uiOpenTypeFeaturesClone(const uiOpenTypeFeatures *otf)
{
uiOpenTypeFeatures *ret;
ret = uiprivNew(uiOpenTypeFeatures);
ret->len = otf->len;
ret->cap = otf->cap;
ret->data = (struct feature *) uiprivAlloc(bytecount(ret->cap), "struct feature[]");
memset(ret->data, 0, bytecount(ret->cap));
memmove(ret->data, otf->data, bytecount(ret->len));
return ret;
}
#define intdiff(a, b) (((int) (a)) - ((int) (b)))
static int featurecmp(const void *a, const void *b)
{
const struct feature *f = (const struct feature *) a;
const struct feature *g = (const struct feature *) b;
if (f->a != g->a)
return intdiff(f->a, g->a);
if (f->b != g->b)
return intdiff(f->b, g->b);
if (f->c != g->c)
return intdiff(f->c, g->c);
return intdiff(f->d, g->d);
}
static struct feature mkkey(char a, char b, char c, char d)
{
struct feature f;
f.a = a;
f.b = b;
f.c = c;
f.d = d;
return f;
}
#define find(pkey, otf) bsearch(pkey, otf->data, otf->len, sizeof (struct feature), featurecmp)
void uiOpenTypeFeaturesAdd(uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value)
{
struct feature *f;
struct feature key;
// replace existing value if any
key = mkkey(a, b, c, d);
f = (struct feature *) find(&key, otf);
if (f != NULL) {
f->value = value;
return;
}
// if we got here, the tag is new
if (otf->len == otf->cap) {
otf->cap *= 2;
otf->data = (struct feature *) uiprivRealloc(otf->data, bytecount(otf->cap), "struct feature[]");
}
f = otf->data + otf->len;
f->a = a;
f->b = b;
f->c = c;
f->d = d;
f->value = value;
// LONGTERM qsort here is overkill
otf->len++;
qsort(otf->data, otf->len, sizeof (struct feature), featurecmp);
}
void uiOpenTypeFeaturesRemove(uiOpenTypeFeatures *otf, char a, char b, char c, char d)
{
struct feature *f;
struct feature key;
ptrdiff_t index;
size_t count;
key = mkkey(a, b, c, d);
f = (struct feature *) find(&key, otf);
if (f == NULL)
return;
index = f - otf->data;
count = otf->len - index - 1;
memmove(f + 1, f, bytecount(count));
otf->len--;
}
int uiOpenTypeFeaturesGet(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t *value)
{
const struct feature *f;
struct feature key;
key = mkkey(a, b, c, d);
f = (const struct feature *) find(&key, otf);
if (f == NULL)
return 0;
*value = f->value;
return 1;
}
void uiOpenTypeFeaturesForEach(const uiOpenTypeFeatures *otf, uiOpenTypeFeaturesForEachFunc f, void *data)
{
size_t n;
const struct feature *p;
uiForEach ret;
p = otf->data;
for (n = 0; n < otf->len; n++) {
ret = (*f)(otf, p->a, p->b, p->c, p->d, p->value, data);
// TODO for all: require exact match?
if (ret == uiForEachStop)
return;
p++;
}
}
int uiprivOpenTypeFeaturesEqual(const uiOpenTypeFeatures *a, const uiOpenTypeFeatures *b)
{
if (a == b)
return 1;
if (a->len != b->len)
return 0;
return memcmp(a->data, b->data, bytecount(a->len)) == 0;
}

View File

@ -8,7 +8,7 @@ static int defaultOnShouldQuit(void *data)
}
static int (*onShouldQuit)(void *) = defaultOnShouldQuit;
static void *onShouldQuitData;
static void *onShouldQuitData = NULL;
void uiOnShouldQuit(int (*f)(void *), void *data)
{
@ -16,7 +16,7 @@ void uiOnShouldQuit(int (*f)(void *), void *data)
onShouldQuitData = data;
}
int shouldQuit(void)
int uiprivShouldQuit(void)
{
return (*onShouldQuit)(onShouldQuitData);
}

View File

@ -1,41 +1,47 @@
// 6 april 2015
// note: this file should not include ui.h, as the OS-specific ui_*.h files are included between that one and this one in the OS-specific uipriv_*.h* files
#include <stdarg.h>
#include <string.h>
#include "controlsigs.h"
#include "utf.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <stdarg.h>
#include "controlsigs.h"
// OS-specific init.* or main.* files
extern uiInitOptions uiprivOptions;
extern uiInitOptions options;
// OS-specific alloc.* files
extern void *uiprivAlloc(size_t, const char *);
#define uiprivNew(T) ((T *) uiprivAlloc(sizeof (T), #T))
extern void *uiprivRealloc(void *, size_t, const char *);
extern void uiprivFree(void *);
extern void *uiAlloc(size_t, const char *);
#define uiNew(T) ((T *) uiAlloc(sizeof (T), #T))
extern void *uiRealloc(void *, size_t, const char *);
extern void uiFree(void *);
// ugh, this was only introduced in MSVC 2015...
// debug.c and OS-specific debug.* files
// TODO get rid of this mess...
// ugh, __func__ was only introduced in MSVC 2015...
#ifdef _MSC_VER
#define __func__ __FUNCTION__
#define uiprivMacro__func__ __FUNCTION__
#else
#define uiprivMacro__func__ __func__
#endif
extern void realbug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap);
#define _ns2(s) #s
#define _ns(s) _ns2(s)
extern void _implbug(const char *file, const char *line, const char *func, const char *format, ...);
#define implbug(...) _implbug(__FILE__, _ns(__LINE__), __func__, __VA_ARGS__)
extern void _userbug(const char *file, const char *line, const char *func, const char *format, ...);
#define userbug(...) _userbug(__FILE__, _ns(__LINE__), __func__, __VA_ARGS__)
// control.c
extern uiControl *newControl(size_t size, uint32_t OSsig, uint32_t typesig, const char *typenamestr);
extern void uiprivRealBug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap);
#define uiprivMacro_ns2(s) #s
#define uiprivMacro_ns(s) uiprivMacro_ns2(s)
extern void uiprivDoImplBug(const char *file, const char *line, const char *func, const char *format, ...);
#define uiprivImplBug(...) uiprivDoImplBug(__FILE__, uiprivMacro_ns(__LINE__), uiprivMacro__func__, __VA_ARGS__)
extern void uiprivDoUserBug(const char *file, const char *line, const char *func, const char *format, ...);
#define uiprivUserBug(...) uiprivDoUserBug(__FILE__, uiprivMacro_ns(__LINE__), uiprivMacro__func__, __VA_ARGS__)
// shouldquit.c
extern int shouldQuit(void);
extern int uiprivShouldQuit(void);
// areaevents.c
typedef struct clickCounter clickCounter;
typedef struct uiprivClickCounter uiprivClickCounter;
// you should call Reset() to zero-initialize a new instance
// it doesn't matter that all the non-count fields are zero: the first click will fail the curButton test straightaway, so it'll return 1 and set the rest of the structure accordingly
struct clickCounter {
struct uiprivClickCounter {
int curButton;
int rectX0;
int rectY0;
@ -44,14 +50,17 @@ struct clickCounter {
uintptr_t prevTime;
int count;
};
int clickCounterClick(clickCounter *c, int button, int x, int y, uintptr_t time, uintptr_t maxTime, int32_t xdist, int32_t ydist);
extern void clickCounterReset(clickCounter *);
extern int fromScancode(uintptr_t, uiAreaKeyEvent *);
extern int uiprivClickCounterClick(uiprivClickCounter *c, int button, int x, int y, uintptr_t time, uintptr_t maxTime, int32_t xdist, int32_t ydist);
extern void uiprivClickCounterReset(uiprivClickCounter *);
extern int uiprivFromScancode(uintptr_t, uiAreaKeyEvent *);
// matrix.c
extern void fallbackSkew(uiDrawMatrix *, double, double, double, double);
extern void scaleCenter(double, double, double *, double *);
extern void fallbackTransformSize(uiDrawMatrix *, double *, double *);
extern void uiprivFallbackSkew(uiDrawMatrix *, double, double, double, double);
extern void uiprivScaleCenter(double, double, double *, double *);
extern void uiprivFallbackTransformSize(uiDrawMatrix *, double *, double *);
// OS-specific text.* files
extern int uiprivStricmp(const char *a, const char *b);
#ifdef __cplusplus
}

View File

@ -4,5 +4,5 @@
void uiUserBugCannotSetParentOnToplevel(const char *type)
{
userbug("You cannot make a %s a child of another uiControl,", type);
uiprivUserBug("You cannot make a %s a child of another uiControl,", type);
}

348
common/utf.c Normal file
View File

@ -0,0 +1,348 @@
// utf by pietro gagliardi (andlabs) — https://github.com/andlabs/utf/
// 10 november 2016
// function names have been altered to avoid namespace collisions in libui static builds (see utf.h)
#include "utf.h"
// this code imitates Go's unicode/utf8 and unicode/utf16
// the biggest difference is that a rune is unsigned instead of signed (because Go guarantees what a right shift on a signed number will do, whereas C does not)
// it is also an imitation so we can license it under looser terms than the Go source
#define badrune 0xFFFD
// encoded must be at most 4 bytes
// TODO clean this code up somehow
size_t uiprivUTF8EncodeRune(uint32_t rune, char *encoded)
{
uint8_t b, c, d, e;
size_t n;
// not in the valid range for Unicode
if (rune > 0x10FFFF)
rune = badrune;
// surrogate runes cannot be encoded
if (rune >= 0xD800 && rune < 0xE000)
rune = badrune;
if (rune < 0x80) { // ASCII bytes represent themselves
b = (uint8_t) (rune & 0xFF);
n = 1;
goto done;
}
if (rune < 0x800) { // two-byte encoding
c = (uint8_t) (rune & 0x3F);
c |= 0x80;
rune >>= 6;
b = (uint8_t) (rune & 0x1F);
b |= 0xC0;
n = 2;
goto done;
}
if (rune < 0x10000) { // three-byte encoding
d = (uint8_t) (rune & 0x3F);
d |= 0x80;
rune >>= 6;
c = (uint8_t) (rune & 0x3F);
c |= 0x80;
rune >>= 6;
b = (uint8_t) (rune & 0x0F);
b |= 0xE0;
n = 3;
goto done;
}
// otherwise use a four-byte encoding
e = (uint8_t) (rune & 0x3F);
e |= 0x80;
rune >>= 6;
d = (uint8_t) (rune & 0x3F);
d |= 0x80;
rune >>= 6;
c = (uint8_t) (rune & 0x3F);
c |= 0x80;
rune >>= 6;
b = (uint8_t) (rune & 0x07);
b |= 0xF0;
n = 4;
done:
encoded[0] = b;
if (n > 1)
encoded[1] = c;
if (n > 2)
encoded[2] = d;
if (n > 3)
encoded[3] = e;
return n;
}
const char *uiprivUTF8DecodeRune(const char *s, size_t nElem, uint32_t *rune)
{
uint8_t b, c;
uint8_t lowestAllowed, highestAllowed;
size_t i, expected;
int bad;
b = (uint8_t) (*s);
if (b < 0x80) { // ASCII bytes represent themselves
*rune = b;
s++;
return s;
}
// 0xC0 and 0xC1 cover 2-byte overlong equivalents
// 0xF5 to 0xFD cover values > 0x10FFFF
// 0xFE and 0xFF were never defined (always illegal)
if (b < 0xC2 || b > 0xF4) { // invalid
*rune = badrune;
s++;
return s;
}
// this determines the range of allowed first continuation bytes
lowestAllowed = 0x80;
highestAllowed = 0xBF;
switch (b) {
case 0xE0:
// disallow 3-byte overlong equivalents
lowestAllowed = 0xA0;
break;
case 0xED:
// disallow surrogate characters
highestAllowed = 0x9F;
break;
case 0xF0:
// disallow 4-byte overlong equivalents
lowestAllowed = 0x90;
break;
case 0xF4:
// disallow values > 0x10FFFF
highestAllowed = 0x8F;
break;
}
// and this determines how many continuation bytes are expected
expected = 1;
if (b >= 0xE0)
expected++;
if (b >= 0xF0)
expected++;
if (nElem != 0) { // are there enough bytes?
nElem--;
if (nElem < expected) { // nope
*rune = badrune;
s++;
return s;
}
}
// ensure that everything is correct
// if not, **only** consume the initial byte
bad = 0;
for (i = 0; i < expected; i++) {
c = (uint8_t) (s[1 + i]);
if (c < lowestAllowed || c > highestAllowed) {
bad = 1;
break;
}
// the old lowestAllowed and highestAllowed is only for the first continuation byte
lowestAllowed = 0x80;
highestAllowed = 0xBF;
}
if (bad) {
*rune = badrune;
s++;
return s;
}
// now do the topmost bits
if (b < 0xE0)
*rune = b & 0x1F;
else if (b < 0xF0)
*rune = b & 0x0F;
else
*rune = b & 0x07;
s++; // we can finally move on
// now do the continuation bytes
for (; expected; expected--) {
c = (uint8_t) (*s);
s++;
c &= 0x3F; // strip continuation bits
*rune <<= 6;
*rune |= c;
}
return s;
}
// encoded must have at most 2 elements
size_t uiprivUTF16EncodeRune(uint32_t rune, uint16_t *encoded)
{
uint16_t low, high;
// not in the valid range for Unicode
if (rune > 0x10FFFF)
rune = badrune;
// surrogate runes cannot be encoded
if (rune >= 0xD800 && rune < 0xE000)
rune = badrune;
if (rune < 0x10000) {
encoded[0] = (uint16_t) rune;
return 1;
}
rune -= 0x10000;
low = (uint16_t) (rune & 0x3FF);
rune >>= 10;
high = (uint16_t) (rune & 0x3FF);
encoded[0] = high | 0xD800;
encoded[1] = low | 0xDC00;
return 2;
}
// TODO see if this can be cleaned up somehow
const uint16_t *uiprivUTF16DecodeRune(const uint16_t *s, size_t nElem, uint32_t *rune)
{
uint16_t high, low;
if (*s < 0xD800 || *s >= 0xE000) {
// self-representing character
*rune = *s;
s++;
return s;
}
if (*s >= 0xDC00) {
// out-of-order surrogates
*rune = badrune;
s++;
return s;
}
if (nElem == 1) { // not enough elements
*rune = badrune;
s++;
return s;
}
high = *s;
high &= 0x3FF;
if (s[1] < 0xDC00 || s[1] >= 0xE000) {
// bad surrogate pair
*rune = badrune;
s++;
return s;
}
s++;
low = *s;
s++;
low &= 0x3FF;
*rune = high;
*rune <<= 10;
*rune |= low;
*rune += 0x10000;
return s;
}
// TODO find a way to reduce the code in all of these somehow
// TODO find a way to remove u as well
size_t uiprivUTF8RuneCount(const char *s, size_t nElem)
{
size_t len;
uint32_t rune;
if (nElem != 0) {
const char *t, *u;
len = 0;
t = s;
while (nElem != 0) {
u = uiprivUTF8DecodeRune(t, nElem, &rune);
len++;
nElem -= u - t;
t = u;
}
return len;
}
len = 0;
while (*s) {
s = uiprivUTF8DecodeRune(s, nElem, &rune);
len++;
}
return len;
}
size_t uiprivUTF8UTF16Count(const char *s, size_t nElem)
{
size_t len;
uint32_t rune;
uint16_t encoded[2];
if (nElem != 0) {
const char *t, *u;
len = 0;
t = s;
while (nElem != 0) {
u = uiprivUTF8DecodeRune(t, nElem, &rune);
len += uiprivUTF16EncodeRune(rune, encoded);
nElem -= u - t;
t = u;
}
return len;
}
len = 0;
while (*s) {
s = uiprivUTF8DecodeRune(s, nElem, &rune);
len += uiprivUTF16EncodeRune(rune, encoded);
}
return len;
}
size_t uiprivUTF16RuneCount(const uint16_t *s, size_t nElem)
{
size_t len;
uint32_t rune;
if (nElem != 0) {
const uint16_t *t, *u;
len = 0;
t = s;
while (nElem != 0) {
u = uiprivUTF16DecodeRune(t, nElem, &rune);
len++;
nElem -= u - t;
t = u;
}
return len;
}
len = 0;
while (*s) {
s = uiprivUTF16DecodeRune(s, nElem, &rune);
len++;
}
return len;
}
size_t uiprivUTF16UTF8Count(const uint16_t *s, size_t nElem)
{
size_t len;
uint32_t rune;
char encoded[4];
if (nElem != 0) {
const uint16_t *t, *u;
len = 0;
t = s;
while (nElem != 0) {
u = uiprivUTF16DecodeRune(t, nElem, &rune);
len += uiprivUTF8EncodeRune(rune, encoded);
nElem -= u - t;
t = u;
}
return len;
}
len = 0;
while (*s) {
s = uiprivUTF16DecodeRune(s, nElem, &rune);
len += uiprivUTF8EncodeRune(rune, encoded);
}
return len;
}

65
common/utf.h Normal file
View File

@ -0,0 +1,65 @@
// utf by pietro gagliardi (andlabs) — https://github.com/andlabs/utf/
// 10 november 2016
// note the overridden names with uipriv at the beginning; this avoids potential symbol clashes when building libui as a static library
// LONGTERM find a way to encode the name overrides directly into the utf library
#ifdef __cplusplus
extern "C" {
#endif
// TODO (for utf itself as well) should this go outside the extern "C" block or not
#include <stddef.h>
#include <stdint.h>
// if nElem == 0, assume the buffer has no upper limit and is '\0' terminated
// otherwise, assume buffer is NOT '\0' terminated but is bounded by nElem *elements*
extern size_t uiprivUTF8EncodeRune(uint32_t rune, char *encoded);
extern const char *uiprivUTF8DecodeRune(const char *s, size_t nElem, uint32_t *rune);
extern size_t uiprivUTF16EncodeRune(uint32_t rune, uint16_t *encoded);
extern const uint16_t *uiprivUTF16DecodeRune(const uint16_t *s, size_t nElem, uint32_t *rune);
extern size_t uiprivUTF8RuneCount(const char *s, size_t nElem);
extern size_t uiprivUTF8UTF16Count(const char *s, size_t nElem);
extern size_t uiprivUTF16RuneCount(const uint16_t *s, size_t nElem);
extern size_t uiprivUTF16UTF8Count(const uint16_t *s, size_t nElem);
#ifdef __cplusplus
}
// Provide overloads on Windows for using these functions with wchar_t and WCHAR when wchar_t is a keyword in C++ mode (the default).
// Otherwise, you'd need to cast to pass a wchar_t pointer, WCHAR pointer, or equivalent to these functions.
// We use __wchar_t to be independent of the setting; see https://blogs.msdn.microsoft.com/oldnewthing/20161201-00/?p=94836 (ironically posted one day after I initially wrote this code!).
// TODO check this on MinGW-w64
// TODO check this under /Wall
// TODO C-style casts enough? or will that fail in /Wall?
// TODO same for UniChar/unichar on Mac? if both are unsigned then we have nothing to worry about
#if defined(_MSC_VER)
inline size_t uiprivUTF16EncodeRune(uint32_t rune, __wchar_t *encoded)
{
return uiprivUTF16EncodeRune(rune, reinterpret_cast<uint16_t *>(encoded));
}
inline const __wchar_t *uiprivUTF16DecodeRune(const __wchar_t *s, size_t nElem, uint32_t *rune)
{
const uint16_t *ret;
ret = uiprivUTF16DecodeRune(reinterpret_cast<const uint16_t *>(s), nElem, rune);
return reinterpret_cast<const __wchar_t *>(ret);
}
inline size_t uiprivUTF16RuneCount(const __wchar_t *s, size_t nElem)
{
return uiprivUTF16RuneCount(reinterpret_cast<const uint16_t *>(s), nElem);
}
inline size_t uiprivUTF16UTF8Count(const __wchar_t *s, size_t nElem)
{
return uiprivUTF16UTF8Count(reinterpret_cast<const uint16_t *>(s), nElem);
}
#endif
#endif

View File

@ -1,9 +1,11 @@
# 3 june 2016
list(APPEND _LIBUI_SOURCES
darwin/aat.m
darwin/alloc.m
darwin/area.m
darwin/areaevents.m
darwin/attrstr.m
darwin/autolayout.m
darwin/box.m
darwin/button.m
@ -18,7 +20,12 @@ list(APPEND _LIBUI_SOURCES
darwin/editablecombo.m
darwin/entry.m
darwin/fontbutton.m
darwin/fontmatch.m
darwin/fonttraits.m
darwin/fontvariation.m
darwin/form.m
darwin/future.m
darwin/graphemes.m
darwin/grid.m
darwin/group.m
darwin/image.m
@ -27,6 +34,7 @@ list(APPEND _LIBUI_SOURCES
darwin/map.m
darwin/menu.m
darwin/multilineentry.m
darwin/opentype.m
darwin/progressbar.m
darwin/radiobuttons.m
darwin/scrollview.m
@ -36,12 +44,14 @@ list(APPEND _LIBUI_SOURCES
darwin/stddialogs.m
darwin/tab.m
darwin/text.m
darwin/undocumented.m
darwin/util.m
darwin/window.m
darwin/winmoveresize.m
)
set(_LIBUI_SOURCES ${_LIBUI_SOURCES} PARENT_SCOPE)
# TODO is this correct?
list(APPEND _LIBUI_INCLUDEDIRS
darwin
)
@ -52,6 +62,7 @@ if(NOT BUILD_SHARED_LIBS)
set(_LIBUINAME libui-temporary PARENT_SCOPE)
endif()
# thanks to Mr-Hide in irc.freenode.net/#cmake
# TODO remove all these temporary files after linking the final archive file
macro(_handle_static)
set_target_properties(${_LIBUINAME} PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")

403
darwin/aat.m Normal file
View File

@ -0,0 +1,403 @@
// 14 february 2017
#import "uipriv_darwin.h"
#import "attrstr.h"
// TODO explain the purpose of this file
static void boolspec(uint32_t value, uint16_t type, uint16_t ifTrue, uint16_t ifFalse, uiprivAATBlock f)
{
// TODO are values other than 1 accepted for true by OpenType itself? (same for the rest of the file)
if (value != 0) {
f(type, ifTrue);
return;
}
f(type, ifFalse);
}
// TODO remove the need for this
// TODO remove x8tox32()
#define x8tox32(x) ((uint32_t) (((uint8_t) (x)) & 0xFF))
#define mkTag(a, b, c, d) \
((x8tox32(a) << 24) | \
(x8tox32(b) << 16) | \
(x8tox32(c) << 8) | \
x8tox32(d))
// TODO double-check drawtext example to make sure all of these are used properly (I already screwed dlig up by putting clig twice instead)
void uiprivOpenTypeToAAT(char a, char b, char c, char d, uint32_t value, uiprivAATBlock f)
{
switch (mkTag(a, b, c, d)) {
case mkTag('l', 'i', 'g', 'a'):
boolspec(value, kLigaturesType,
kCommonLigaturesOnSelector,
kCommonLigaturesOffSelector,
f);
break;
case mkTag('r', 'l', 'i', 'g'):
boolspec(value, kLigaturesType,
kRequiredLigaturesOnSelector,
kRequiredLigaturesOffSelector,
f);
break;
case mkTag('d', 'l', 'i', 'g'):
boolspec(value, kLigaturesType,
kRareLigaturesOnSelector,
kRareLigaturesOffSelector,
f);
break;
case mkTag('c', 'l', 'i', 'g'):
boolspec(value, kLigaturesType,
kContextualLigaturesOnSelector,
kContextualLigaturesOffSelector,
f);
break;
case mkTag('h', 'l', 'i', 'g'):
// This technically isn't what is meant by "historical ligatures", but Core Text's internal AAT-to-OpenType mapping says to include it, so we include it too
case mkTag('h', 'i', 's', 't'):
boolspec(value, kLigaturesType,
kHistoricalLigaturesOnSelector,
kHistoricalLigaturesOffSelector,
f);
break;
case mkTag('u', 'n', 'i', 'c'):
// TODO is this correct, or should we provide an else case?
if (value != 0)
// this is undocumented; it comes from Core Text's internal AAT-to-OpenType conversion table
f(kLetterCaseType, 14);
break;
// TODO will the following handle all cases properly, or are elses going to be needed?
case mkTag('p', 'n', 'u', 'm'):
if (value != 0)
f(kNumberSpacingType, kProportionalNumbersSelector);
break;
case mkTag('t', 'n', 'u', 'm'):
if (value != 0)
f(kNumberSpacingType, kMonospacedNumbersSelector);
break;
// TODO will the following handle all cases properly, or are elses going to be needed?
case mkTag('s', 'u', 'p', 's'):
if (value != 0)
f(kVerticalPositionType, kSuperiorsSelector);
break;
case mkTag('s', 'u', 'b', 's'):
if (value != 0)
f(kVerticalPositionType, kInferiorsSelector);
break;
case mkTag('o', 'r', 'd', 'n'):
if (value != 0)
f(kVerticalPositionType, kOrdinalsSelector);
break;
case mkTag('s', 'i', 'n', 'f'):
if (value != 0)
f(kVerticalPositionType, kScientificInferiorsSelector);
break;
// TODO will the following handle all cases properly, or are elses going to be needed?
case mkTag('a', 'f', 'r', 'c'):
if (value != 0)
f(kFractionsType, kVerticalFractionsSelector);
break;
case mkTag('f', 'r', 'a', 'c'):
if (value != 0)
f(kFractionsType, kDiagonalFractionsSelector);
break;
case mkTag('z', 'e', 'r', 'o'):
boolspec(value, kTypographicExtrasType,
kSlashedZeroOnSelector,
kSlashedZeroOffSelector,
f);
break;
case mkTag('m', 'g', 'r', 'k'):
boolspec(value, kMathematicalExtrasType,
kMathematicalGreekOnSelector,
kMathematicalGreekOffSelector,
f);
break;
case mkTag('o', 'r', 'n', 'm'):
f(kOrnamentSetsType, (uint16_t) value);
break;
case mkTag('a', 'a', 'l', 't'):
f(kCharacterAlternativesType, (uint16_t) value);
break;
case mkTag('t', 'i', 't', 'l'):
// TODO is this correct, or should we provide an else case?
if (value != 0)
f(kStyleOptionsType, kTitlingCapsSelector);
break;
// TODO will the following handle all cases properly, or are elses going to be needed?
case mkTag('t', 'r', 'a', 'd'):
if (value != 0)
f(kCharacterShapeType, kTraditionalCharactersSelector);
break;
case mkTag('s', 'm', 'p', 'l'):
if (value != 0)
f(kCharacterShapeType, kSimplifiedCharactersSelector);
break;
case mkTag('j', 'p', '7', '8'):
if (value != 0)
f(kCharacterShapeType, kJIS1978CharactersSelector);
break;
case mkTag('j', 'p', '8', '3'):
if (value != 0)
f(kCharacterShapeType, kJIS1983CharactersSelector);
break;
case mkTag('j', 'p', '9', '0'):
if (value != 0)
f(kCharacterShapeType, kJIS1990CharactersSelector);
break;
case mkTag('e', 'x', 'p', 't'):
if (value != 0)
f(kCharacterShapeType, kExpertCharactersSelector);
break;
case mkTag('j', 'p', '0', '4'):
if (value != 0)
f(kCharacterShapeType, kJIS2004CharactersSelector);
break;
case mkTag('h', 'o', 'j', 'o'):
if (value != 0)
f(kCharacterShapeType, kHojoCharactersSelector);
break;
case mkTag('n', 'l', 'c', 'k'):
if (value != 0)
f(kCharacterShapeType, kNLCCharactersSelector);
break;
case mkTag('t', 'n', 'a', 'm'):
if (value != 0)
f(kCharacterShapeType, kTraditionalNamesCharactersSelector);
break;
case mkTag('o', 'n', 'u', 'm'):
// Core Text's internal AAT-to-OpenType mapping says to include this, so we include it too
// TODO is it always set?
case mkTag('l', 'n', 'u', 'm'):
// TODO is this correct, or should we provide an else case?
if (value != 0)
f(kNumberCaseType, kLowerCaseNumbersSelector);
break;
case mkTag('h', 'n', 'g', 'l'):
// TODO is this correct, or should we provide an else case?
if (value != 0)
f(kTransliterationType, kHanjaToHangulSelector);
break;
case mkTag('n', 'a', 'l', 't'):
f(kAnnotationType, (uint16_t) value);
break;
case mkTag('r', 'u', 'b', 'y'):
// include this for completeness
boolspec(value, kRubyKanaType,
kRubyKanaSelector,
kNoRubyKanaSelector,
f);
// this is the current one
boolspec(value, kRubyKanaType,
kRubyKanaOnSelector,
kRubyKanaOffSelector,
f);
break;
case mkTag('i', 't', 'a', 'l'):
// include this for completeness
boolspec(value, kItalicCJKRomanType,
kCJKItalicRomanSelector,
kNoCJKItalicRomanSelector,
f);
// this is the current one
boolspec(value, kItalicCJKRomanType,
kCJKItalicRomanOnSelector,
kCJKItalicRomanOffSelector,
f);
break;
case mkTag('c', 'a', 's', 'e'):
boolspec(value, kCaseSensitiveLayoutType,
kCaseSensitiveLayoutOnSelector,
kCaseSensitiveLayoutOffSelector,
f);
break;
case mkTag('c', 'p', 's', 'p'):
boolspec(value, kCaseSensitiveLayoutType,
kCaseSensitiveSpacingOnSelector,
kCaseSensitiveSpacingOffSelector,
f);
break;
case mkTag('h', 'k', 'n', 'a'):
boolspec(value, kAlternateKanaType,
kAlternateHorizKanaOnSelector,
kAlternateHorizKanaOffSelector,
f);
break;
case mkTag('v', 'k', 'n', 'a'):
boolspec(value, kAlternateKanaType,
kAlternateVertKanaOnSelector,
kAlternateVertKanaOffSelector,
f);
break;
case mkTag('s', 's', '0', '1'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltOneOnSelector,
kStylisticAltOneOffSelector,
f);
break;
case mkTag('s', 's', '0', '2'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltTwoOnSelector,
kStylisticAltTwoOffSelector,
f);
break;
case mkTag('s', 's', '0', '3'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltThreeOnSelector,
kStylisticAltThreeOffSelector,
f);
break;
case mkTag('s', 's', '0', '4'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltFourOnSelector,
kStylisticAltFourOffSelector,
f);
break;
case mkTag('s', 's', '0', '5'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltFiveOnSelector,
kStylisticAltFiveOffSelector,
f);
break;
case mkTag('s', 's', '0', '6'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltSixOnSelector,
kStylisticAltSixOffSelector,
f);
break;
case mkTag('s', 's', '0', '7'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltSevenOnSelector,
kStylisticAltSevenOffSelector,
f);
break;
case mkTag('s', 's', '0', '8'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltEightOnSelector,
kStylisticAltEightOffSelector,
f);
break;
case mkTag('s', 's', '0', '9'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltNineOnSelector,
kStylisticAltNineOffSelector,
f);
break;
case mkTag('s', 's', '1', '0'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltTenOnSelector,
kStylisticAltTenOffSelector,
f);
break;
case mkTag('s', 's', '1', '1'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltElevenOnSelector,
kStylisticAltElevenOffSelector,
f);
break;
case mkTag('s', 's', '1', '2'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltTwelveOnSelector,
kStylisticAltTwelveOffSelector,
f);
break;
case mkTag('s', 's', '1', '3'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltThirteenOnSelector,
kStylisticAltThirteenOffSelector,
f);
break;
case mkTag('s', 's', '1', '4'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltFourteenOnSelector,
kStylisticAltFourteenOffSelector,
f);
break;
case mkTag('s', 's', '1', '5'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltFifteenOnSelector,
kStylisticAltFifteenOffSelector,
f);
break;
case mkTag('s', 's', '1', '6'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltSixteenOnSelector,
kStylisticAltSixteenOffSelector,
f);
break;
case mkTag('s', 's', '1', '7'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltSeventeenOnSelector,
kStylisticAltSeventeenOffSelector,
f);
break;
case mkTag('s', 's', '1', '8'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltEighteenOnSelector,
kStylisticAltEighteenOffSelector,
f);
break;
case mkTag('s', 's', '1', '9'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltNineteenOnSelector,
kStylisticAltNineteenOffSelector,
f);
break;
case mkTag('s', 's', '2', '0'):
boolspec(value, kStylisticAlternativesType,
kStylisticAltTwentyOnSelector,
kStylisticAltTwentyOffSelector,
f);
break;
case mkTag('c', 'a', 'l', 't'):
boolspec(value, kContextualAlternatesType,
kContextualAlternatesOnSelector,
kContextualAlternatesOffSelector,
f);
break;
case mkTag('s', 'w', 's', 'h'):
boolspec(value, kContextualAlternatesType,
kSwashAlternatesOnSelector,
kSwashAlternatesOffSelector,
f);
break;
case mkTag('c', 's', 'w', 'h'):
boolspec(value, kContextualAlternatesType,
kContextualSwashAlternatesOnSelector,
kContextualSwashAlternatesOffSelector,
f);
break;
// TODO will the following handle all cases properly, or are elses going to be needed?
case mkTag('s', 'm', 'c', 'p'):
if (value != 0) {
// include this for compatibility (some fonts that come with OS X still use this!)
// TODO make it boolean?
f(kLetterCaseType, kSmallCapsSelector);
// this is the current one
f(kLowerCaseType, kLowerCaseSmallCapsSelector);
}
break;
case mkTag('p', 'c', 'a', 'p'):
if (value != 0)
f(kLowerCaseType, kLowerCasePetiteCapsSelector);
break;
// TODO will the following handle all cases properly, or are elses going to be needed?
case mkTag('c', '2', 's', 'c'):
if (value != 0)
f(kUpperCaseType, kUpperCaseSmallCapsSelector);
break;
case mkTag('c', '2', 'p', 'c'):
if (value != 0)
f(kUpperCaseType, kUpperCasePetiteCapsSelector);
break;
}
// TODO handle this properly
// (it used to return 0 when this still returned the number of selectors produced but IDK what properly is anymore)
}

View File

@ -37,11 +37,11 @@ void uninitAlloc(void)
ptr = [v pointerValue];
[str appendString:[NSString stringWithFormat:@"%p %s\n", ptr, *TYPE(ptr)]];
}
userbug("Some data was leaked; either you left a uiControl lying around or there's a bug in libui itself. Leaked data:\n%s", [str UTF8String]);
uiprivUserBug("Some data was leaked; either you left a uiControl lying around or there's a bug in libui itself. Leaked data:\n%s", [str UTF8String]);
[str release];
}
void *uiAlloc(size_t size, const char *type)
void *uiprivAlloc(size_t size, const char *type)
{
void *out;
@ -57,21 +57,21 @@ void *uiAlloc(size_t size, const char *type)
return DATA(out);
}
void *uiRealloc(void *p, size_t new, const char *type)
void *uiprivRealloc(void *p, size_t new, const char *type)
{
void *out;
size_t *s;
if (p == NULL)
return uiAlloc(new, type);
return uiprivAlloc(new, type);
p = BASE(p);
out = realloc(p, EXTRA + new);
if (out == NULL) {
fprintf(stderr, "memory exhausted in uiRealloc()\n");
fprintf(stderr, "memory exhausted in uiprivRealloc()\n");
abort();
}
s = SIZE(out);
if (new <= *s)
if (new > *s)
memset(((uint8_t *) DATA(out)) + *s, 0, new - *s);
*s = new;
[allocations removeObject:[NSValue valueWithPointer:p]];
@ -79,10 +79,10 @@ void *uiRealloc(void *p, size_t new, const char *type)
return DATA(out);
}
void uiFree(void *p)
void uiprivFree(void *p)
{
if (p == NULL)
implbug("attempt to uiFree(NULL)");
uiprivImplBug("attempt to uiprivFree(NULL)");
p = BASE(p);
free(p);
[allocations removeObject:[NSValue valueWithPointer:p]];

View File

@ -390,7 +390,7 @@ int sendAreaEvents(NSEvent *e)
void uiAreaSetSize(uiArea *a, int width, int height)
{
if (!a->scrolling)
userbug("You cannot call uiAreaSetSize() on a non-scrolling uiArea. (area: %p)", a);
uiprivUserBug("You cannot call uiAreaSetSize() on a non-scrolling uiArea. (area: %p)", a);
[a->area setScrollingSize:NSMakeSize(width, height)];
}
@ -402,7 +402,7 @@ void uiAreaQueueRedrawAll(uiArea *a)
void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height)
{
if (!a->scrolling)
userbug("You cannot call uiAreaScrollTo() on a non-scrolling uiArea. (area: %p)", a);
uiprivUserBug("You cannot call uiAreaScrollTo() on a non-scrolling uiArea. (area: %p)", a);
[a->area scrollRectToVisible:NSMakeRect(x, y, width, height)];
// don't worry about the return value; it just says whether scrolling was needed
}

91
darwin/attrstr.h Normal file
View File

@ -0,0 +1,91 @@
// 4 march 2018
#import "../common/attrstr.h"
// opentype.m
extern CFArrayRef uiprivOpenTypeFeaturesToCTFeatures(const uiOpenTypeFeatures *otf);
// aat.m
typedef void (^uiprivAATBlock)(uint16_t type, uint16_t selector);
extern void uiprivOpenTypeToAAT(char a, char b, char c, char d, uint32_t value, uiprivAATBlock f);
// fontmatch.m
@interface uiprivFontStyleData : NSObject {
CTFontRef font;
CTFontDescriptorRef desc;
CFDictionaryRef traits;
CTFontSymbolicTraits symbolic;
double weight;
double width;
BOOL didStyleName;
CFStringRef styleName;
BOOL didVariation;
CFDictionaryRef variation;
BOOL hasRegistrationScope;
CTFontManagerScope registrationScope;
BOOL didPostScriptName;
CFStringRef postScriptName;
CTFontFormat fontFormat;
BOOL didPreferredSubFamilyName;
CFStringRef preferredSubFamilyName;
BOOL didSubFamilyName;
CFStringRef subFamilyName;
BOOL didFullName;
CFStringRef fullName;
BOOL didPreferredFamilyName;
CFStringRef preferredFamilyName;
BOOL didFamilyName;
CFStringRef familyName;
BOOL didVariationAxes;
CFArrayRef variationAxes;
}
- (id)initWithFont:(CTFontRef)f;
- (id)initWithDescriptor:(CTFontDescriptorRef)d;
- (BOOL)prepare;
- (void)ensureFont;
- (CTFontSymbolicTraits)symbolicTraits;
- (double)weight;
- (double)width;
- (CFStringRef)styleName;
- (CFDictionaryRef)variation;
- (BOOL)hasRegistrationScope;
- (CTFontManagerScope)registrationScope;
- (CFStringRef)postScriptName;
- (CFDataRef)table:(CTFontTableTag)tag;
- (CTFontFormat)fontFormat;
- (CFStringRef)fontName:(CFStringRef)key;
- (CFStringRef)preferredSubFamilyName;
- (CFStringRef)subFamilyName;
- (CFStringRef)fullName;
- (CFStringRef)preferredFamilyName;
- (CFStringRef)familyName;
- (CFArrayRef)variationAxes;
@end
extern CTFontDescriptorRef uiprivFontDescriptorToCTFontDescriptor(uiFontDescriptor *fd);
extern CTFontDescriptorRef uiprivCTFontDescriptorAppendFeatures(CTFontDescriptorRef desc, const uiOpenTypeFeatures *otf);
extern void uiprivFontDescriptorFromCTFontDescriptor(CTFontDescriptorRef ctdesc, uiFontDescriptor *uidesc);
// fonttraits.m
extern void uiprivProcessFontTraits(uiprivFontStyleData *d, uiFontDescriptor *out);
// fontvariation.m
extern NSDictionary *uiprivMakeVariationAxisDict(CFArrayRef axes, CFDataRef avarTable);
extern void uiprivProcessFontVariation(uiprivFontStyleData *d, NSDictionary *axisDict, uiFontDescriptor *out);
// attrstr.m
extern void uiprivInitUnderlineColors(void);
extern void uiprivUninitUnderlineColors(void);
extern CFAttributedStringRef uiprivAttributedStringToCFAttributedString(uiDrawTextLayoutParams *p, NSArray **backgroundParams);
// drawtext.m
// TODO figure out where this type should *really* go in all the headers...
@interface uiprivDrawTextBackgroundParams : NSObject {
size_t start;
size_t end;
double r;
double g;
double b;
double a;
}
- (id)initWithStart:(size_t)s end:(size_t)e r:(double)red g:(double)green b:(double)blue a:(double)alpha;
- (void)draw:(CGContextRef)c layout:(uiDrawTextLayout *)layout at:(double)x y:(double)y utf8Mapping:(const size_t *)u16tou8;
@end

505
darwin/attrstr.m Normal file
View File

@ -0,0 +1,505 @@
// 12 february 2017
#import "uipriv_darwin.h"
#import "attrstr.h"
// this is what AppKit does internally
// WebKit does this too; see https://github.com/adobe/webkit/blob/master/Source/WebCore/platform/graphics/mac/GraphicsContextMac.mm
static NSColor *spellingColor = nil;
static NSColor *grammarColor = nil;
static NSColor *auxiliaryColor = nil;
static NSColor *tryColorNamed(NSString *name)
{
NSImage *img;
img = [NSImage imageNamed:name];
if (img == nil)
return nil;
return [NSColor colorWithPatternImage:img];
}
void uiprivInitUnderlineColors(void)
{
spellingColor = tryColorNamed(@"NSSpellingDot");
if (spellingColor == nil) {
// WebKit says this is needed for "older systems"; not sure how old, but 10.11 AppKit doesn't look for this
spellingColor = tryColorNamed(@"SpellingDot");
if (spellingColor == nil)
spellingColor = [NSColor redColor];
}
[spellingColor retain]; // override autoreleasing
grammarColor = tryColorNamed(@"NSGrammarDot");
if (grammarColor == nil) {
// WebKit says this is needed for "older systems"; not sure how old, but 10.11 AppKit doesn't look for this
grammarColor = tryColorNamed(@"GrammarDot");
if (grammarColor == nil)
grammarColor = [NSColor greenColor];
}
[grammarColor retain]; // override autoreleasing
auxiliaryColor = tryColorNamed(@"NSCorrectionDot");
if (auxiliaryColor == nil) {
// WebKit says this is needed for "older systems"; not sure how old, but 10.11 AppKit doesn't look for this
auxiliaryColor = tryColorNamed(@"CorrectionDot");
if (auxiliaryColor == nil)
auxiliaryColor = [NSColor blueColor];
}
[auxiliaryColor retain]; // override autoreleasing
}
void uiprivUninitUnderlineColors(void)
{
[auxiliaryColor release];
auxiliaryColor = nil;
[grammarColor release];
grammarColor = nil;
[spellingColor release];
spellingColor = nil;
}
// TODO opentype features are lost when using uiFontDescriptor, so a handful of fonts in the font panel ("Titling" variants of some fonts and possibly others but those are the examples I know about) cannot be represented by uiFontDescriptor; what *can* we do about this since this info is NOT part of the font on other platforms?
// TODO see if we could use NSAttributedString?
// TODO consider renaming this struct and the fep variable(s)
// TODO restructure all this so the important details at the top are below with the combined font attributes type?
// TODO in fact I should just write something to explain everything in this file...
struct foreachParams {
CFMutableAttributedStringRef mas;
NSMutableArray *backgroundParams;
};
// unlike the other systems, Core Text rolls family, size, weight, italic, width, AND opentype features into the "font" attribute
// instead of incrementally adjusting CTFontRefs (which, judging from NSFontManager, seems finicky and UI-centric), we use a custom class to incrementally store attributes that go into a CTFontRef, and then convert everything to CTFonts en masse later
// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/AttributedStrings/Tasks/ChangingAttrStrings.html#//apple_ref/doc/uid/20000162-BBCBGCDG says we must have -hash and -isEqual: workign properly for this to work, so we must do that too, using a basic xor-based hash and leveraging Cocoa -hash implementations where useful and feasible (if not necessary)
// TODO structure and rewrite this part
// TODO re-find sources proving support of custom attributes
// TODO what if this is NULL?
static const CFStringRef combinedFontAttrName = CFSTR("libuiCombinedFontAttribute");
enum {
cFamily,
cSize,
cWeight,
cItalic,
cStretch,
cFeatures,
nc,
};
static const int toc[] = {
[uiAttributeTypeFamily] = cFamily,
[uiAttributeTypeSize] = cSize,
[uiAttributeTypeWeight] = cWeight,
[uiAttributeTypeItalic] = cItalic,
[uiAttributeTypeStretch] = cStretch,
[uiAttributeTypeFeatures] = cFeatures,
};
static uiForEach featuresHash(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data)
{
NSUInteger *hash = (NSUInteger *) data;
uint32_t tag;
tag = (((uint32_t) a) & 0xFF) << 24;
tag |= (((uint32_t) b) & 0xFF) << 16;
tag |= (((uint32_t) c) & 0xFF) << 8;
tag |= ((uint32_t) d) & 0xFF;
*hash ^= tag;
*hash ^= value;
return uiForEachContinue;
}
@interface uiprivCombinedFontAttr : NSObject<NSCopying> {
uiAttribute *attrs[nc];
BOOL hasHash;
NSUInteger hash;
}
- (void)addAttribute:(uiAttribute *)attr;
- (CTFontRef)toCTFontWithDefaultFont:(uiFontDescriptor *)defaultFont;
@end
@implementation uiprivCombinedFontAttr
- (id)init
{
self = [super init];
if (self) {
memset(self->attrs, 0, nc * sizeof (uiAttribute *));
self->hasHash = NO;
}
return self;
}
- (void)dealloc
{
int i;
for (i = 0; i < nc; i++)
if (self->attrs[i] != NULL) {
uiprivAttributeRelease(self->attrs[i]);
self->attrs[i] = NULL;
}
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone
{
uiprivCombinedFontAttr *ret;
int i;
ret = [[uiprivCombinedFontAttr allocWithZone:zone] init];
for (i = 0; i < nc; i++)
if (self->attrs[i] != NULL)
ret->attrs[i] = uiprivAttributeRetain(self->attrs[i]);
ret->hasHash = self->hasHash;
ret->hash = self->hash;
return ret;
}
- (void)addAttribute:(uiAttribute *)attr
{
int index;
index = toc[uiAttributeGetType(attr)];
if (self->attrs[index] != NULL)
uiprivAttributeRelease(self->attrs[index]);
self->attrs[index] = uiprivAttributeRetain(attr);
self->hasHash = NO;
}
- (BOOL)isEqual:(id)bb
{
uiprivCombinedFontAttr *b = (uiprivCombinedFontAttr *) bb;
int i;
if (b == nil)
return NO;
for (i = 0; i < nc; i++) {
if (self->attrs[i] == NULL && b->attrs[i] == NULL)
continue;
if (self->attrs[i] == NULL || b->attrs[i] == NULL)
return NO;
if (!uiprivAttributeEqual(self->attrs[i], b->attrs[i]))
return NO;
}
return YES;
}
- (NSUInteger)hash
{
if (self->hasHash)
return self->hash;
@autoreleasepool {
NSString *family;
NSNumber *size;
self->hash = 0;
if (self->attrs[cFamily] != NULL) {
family = [NSString stringWithUTF8String:uiAttributeFamily(self->attrs[cFamily])];
// TODO make sure this aligns with case-insensitive compares when those are done in common/attribute.c
self->hash ^= [[family uppercaseString] hash];
}
if (self->attrs[cSize] != NULL) {
size = [NSNumber numberWithDouble:uiAttributeSize(self->attrs[cSize])];
self->hash ^= [size hash];
}
if (self->attrs[cWeight] != NULL)
self->hash ^= (NSUInteger) uiAttributeWeight(self->attrs[cWeight]);
if (self->attrs[cItalic] != NULL)
self->hash ^= (NSUInteger) uiAttributeItalic(self->attrs[cItalic]);
if (self->attrs[cStretch] != NULL)
self->hash ^= (NSUInteger) uiAttributeStretch(self->attrs[cStretch]);
if (self->attrs[cFeatures] != NULL)
uiOpenTypeFeaturesForEach(uiAttributeFeatures(self->attrs[cFeatures]), featuresHash, &(self->hash));
self->hasHash = YES;
}
return self->hash;
}
- (CTFontRef)toCTFontWithDefaultFont:(uiFontDescriptor *)defaultFont
{
uiFontDescriptor uidesc;
CTFontDescriptorRef desc;
CTFontRef font;
uidesc = *defaultFont;
if (self->attrs[cFamily] != NULL)
// TODO const-correct uiFontDescriptor or change this function below
uidesc.Family = (char *) uiAttributeFamily(self->attrs[cFamily]);
if (self->attrs[cSize] != NULL)
uidesc.Size = uiAttributeSize(self->attrs[cSize]);
if (self->attrs[cWeight] != NULL)
uidesc.Weight = uiAttributeWeight(self->attrs[cWeight]);
if (self->attrs[cItalic] != NULL)
uidesc.Italic = uiAttributeItalic(self->attrs[cItalic]);
if (self->attrs[cStretch] != NULL)
uidesc.Stretch = uiAttributeStretch(self->attrs[cStretch]);
desc = uiprivFontDescriptorToCTFontDescriptor(&uidesc);
if (self->attrs[cFeatures] != NULL)
desc = uiprivCTFontDescriptorAppendFeatures(desc, uiAttributeFeatures(self->attrs[cFeatures]));
font = CTFontCreateWithFontDescriptor(desc, uidesc.Size, NULL);
CFRelease(desc); // TODO correct?
return font;
}
@end
static void addFontAttributeToRange(struct foreachParams *p, size_t start, size_t end, uiAttribute *attr)
{
uiprivCombinedFontAttr *cfa;
CFRange range;
size_t diff;
while (start < end) {
cfa = (uiprivCombinedFontAttr *) CFAttributedStringGetAttribute(p->mas, start, combinedFontAttrName, &range);
if (cfa == nil)
cfa = [uiprivCombinedFontAttr new];
else
cfa = [cfa copy];
[cfa addAttribute:attr];
// clamp range within [start, end)
if (range.location < start) {
diff = start - range.location;
range.location = start;
range.length -= diff;
}
if ((range.location + range.length) > end)
range.length = end - range.location;
CFAttributedStringSetAttribute(p->mas, range, combinedFontAttrName, cfa);
[cfa release];
start += range.length;
}
}
static CGColorRef mkcolor(double r, double g, double b, double a)
{
CGColorSpaceRef colorspace;
CGColorRef color;
CGFloat components[4];
// TODO we should probably just create this once and recycle it throughout program execution...
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
if (colorspace == NULL) {
// TODO
}
components[0] = r;
components[1] = g;
components[2] = b;
components[3] = a;
color = CGColorCreate(colorspace, components);
CFRelease(colorspace);
return color;
}
static void addBackgroundAttribute(struct foreachParams *p, size_t start, size_t end, double r, double g, double b, double a)
{
uiprivDrawTextBackgroundParams *dtb;
// TODO make sure this works properly with line paragraph spacings (after figuring out what that means, of course)
if (FUTURE_kCTBackgroundColorAttributeName != NULL) {
CGColorRef color;
CFRange range;
color = mkcolor(r, g, b, a);
range.location = start;
range.length = end - start;
CFAttributedStringSetAttribute(p->mas, range, *FUTURE_kCTBackgroundColorAttributeName, color);
CFRelease(color);
return;
}
dtb = [[uiprivDrawTextBackgroundParams alloc] initWithStart:start end:end r:r g:g b:b a:a];
[p->backgroundParams addObject:dtb];
[dtb release];
}
static uiForEach processAttribute(const uiAttributedString *s, const uiAttribute *attr, size_t start, size_t end, void *data)
{
struct foreachParams *p = (struct foreachParams *) data;
CFRange range;
CGColorRef color;
int32_t us;
CFNumberRef num;
double r, g, b, a;
uiUnderlineColor colorType;
start = uiprivAttributedStringUTF8ToUTF16(s, start);
end = uiprivAttributedStringUTF8ToUTF16(s, end);
range.location = start;
range.length = end - start;
switch (uiAttributeGetType(attr)) {
case uiAttributeTypeFamily:
case uiAttributeTypeSize:
case uiAttributeTypeWeight:
case uiAttributeTypeItalic:
case uiAttributeTypeStretch:
case uiAttributeTypeFeatures:
addFontAttributeToRange(p, start, end, attr);
break;
case uiAttributeTypeColor:
uiAttributeColor(attr, &r, &g, &b, &a);
color = mkcolor(r, g, b, a);
CFAttributedStringSetAttribute(p->mas, range, kCTForegroundColorAttributeName, color);
CFRelease(color);
break;
case uiAttributeTypeBackground:
uiAttributeColor(attr, &r, &g, &b, &a);
addBackgroundAttribute(p, start, end, r, g, b, a);
break;
// TODO turn into a class, like we did with the font attributes, or even integrate *into* the font attributes
case uiAttributeTypeUnderline:
switch (uiAttributeUnderline(attr)) {
case uiUnderlineNone:
us = kCTUnderlineStyleNone;
break;
case uiUnderlineSingle:
us = kCTUnderlineStyleSingle;
break;
case uiUnderlineDouble:
us = kCTUnderlineStyleDouble;
break;
case uiUnderlineSuggestion:
// TODO incorrect if a solid color
us = kCTUnderlineStyleThick;
break;
}
num = CFNumberCreate(NULL, kCFNumberSInt32Type, &us);
CFAttributedStringSetAttribute(p->mas, range, kCTUnderlineStyleAttributeName, num);
CFRelease(num);
break;
case uiAttributeTypeUnderlineColor:
uiAttributeUnderlineColor(attr, &colorType, &r, &g, &b, &a);
switch (colorType) {
case uiUnderlineColorCustom:
color = mkcolor(r, g, b, a);
break;
case uiUnderlineColorSpelling:
color = [spellingColor CGColor];
break;
case uiUnderlineColorGrammar:
color = [grammarColor CGColor];
break;
case uiUnderlineColorAuxiliary:
color = [auxiliaryColor CGColor];
break;
}
CFAttributedStringSetAttribute(p->mas, range, kCTUnderlineColorAttributeName, color);
if (colorType == uiUnderlineColorCustom)
CFRelease(color);
break;
}
return uiForEachContinue;
}
static void applyFontAttributes(CFMutableAttributedStringRef mas, uiFontDescriptor *defaultFont)
{
uiprivCombinedFontAttr *cfa;
CTFontRef font;
CFRange range;
CFIndex n;
n = CFAttributedStringGetLength(mas);
// first apply the default font to the entire string
// TODO is this necessary given the #if 0'd code in uiprivAttributedStringToCFAttributedString()?
cfa = [uiprivCombinedFontAttr new];
font = [cfa toCTFontWithDefaultFont:defaultFont];
[cfa release];
range.location = 0;
range.length = n;
CFAttributedStringSetAttribute(mas, range, kCTFontAttributeName, font);
CFRelease(font);
// now go through, replacing every uiprivCombinedFontAttr with the proper CTFontRef
// we are best off treating series of identical fonts as single ranges ourselves for parity across platforms, even if OS X does something similar itself
range.location = 0;
while (range.location < n) {
// TODO consider seeing if CFAttributedStringGetAttributeAndLongestEffectiveRange() can make things faster by reducing the number of potential iterations, either here or above
cfa = (uiprivCombinedFontAttr *) CFAttributedStringGetAttribute(mas, range.location, combinedFontAttrName, &range);
if (cfa != nil) {
font = [cfa toCTFontWithDefaultFont:defaultFont];
CFAttributedStringSetAttribute(mas, range, kCTFontAttributeName, font);
CFRelease(font);
}
range.location += range.length;
}
// and finally, get rid of all the uiprivCombinedFontAttrs as we won't need them anymore
range.location = 0;
range.length = 0;
CFAttributedStringRemoveAttribute(mas, range, combinedFontAttrName);
}
static const CTTextAlignment ctaligns[] = {
[uiDrawTextAlignLeft] = kCTTextAlignmentLeft,
[uiDrawTextAlignCenter] = kCTTextAlignmentCenter,
[uiDrawTextAlignRight] = kCTTextAlignmentRight,
};
static CTParagraphStyleRef mkParagraphStyle(uiDrawTextLayoutParams *p)
{
CTParagraphStyleRef ps;
CTParagraphStyleSetting settings[16];
size_t nSettings = 0;
settings[nSettings].spec = kCTParagraphStyleSpecifierAlignment;
settings[nSettings].valueSize = sizeof (CTTextAlignment);
settings[nSettings].value = ctaligns + p->Align;
nSettings++;
ps = CTParagraphStyleCreate(settings, nSettings);
if (ps == NULL) {
// TODO
}
return ps;
}
// TODO either rename this (on all platforms) to uiprivDrawTextLayoutParams... or rename this file or both or split the struct or something else...
CFAttributedStringRef uiprivAttributedStringToCFAttributedString(uiDrawTextLayoutParams *p, NSArray **backgroundParams)
{
CFStringRef cfstr;
CFMutableDictionaryRef defaultAttrs;
CTParagraphStyleRef ps;
CFAttributedStringRef base;
CFMutableAttributedStringRef mas;
struct foreachParams fep;
cfstr = CFStringCreateWithCharacters(NULL, uiprivAttributedStringUTF16String(p->String), uiprivAttributedStringUTF16Len(p->String));
if (cfstr == NULL) {
// TODO
}
defaultAttrs = CFDictionaryCreateMutable(NULL, 0,
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (defaultAttrs == NULL) {
// TODO
}
#if 0 /* TODO */
ffp.desc = *(p->DefaultFont);
defaultCTFont = fontdescToCTFont(&ffp);
CFDictionaryAddValue(defaultAttrs, kCTFontAttributeName, defaultCTFont);
CFRelease(defaultCTFont);
#endif
ps = mkParagraphStyle(p);
CFDictionaryAddValue(defaultAttrs, kCTParagraphStyleAttributeName, ps);
CFRelease(ps);
base = CFAttributedStringCreate(NULL, cfstr, defaultAttrs);
if (base == NULL) {
// TODO
}
CFRelease(cfstr);
CFRelease(defaultAttrs);
mas = CFAttributedStringCreateMutableCopy(NULL, 0, base);
CFRelease(base);
CFAttributedStringBeginEditing(mas);
fep.mas = mas;
fep.backgroundParams = [NSMutableArray new];
uiAttributedStringForEachAttribute(p->String, processAttribute, &fep);
applyFontAttributes(mas, p->DefaultFont);
CFAttributedStringEndEditing(mas);
*backgroundParams = fep.backgroundParams;
return mas;
}

View File

@ -12,9 +12,7 @@ NSLayoutConstraint *mkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRela
attribute:attr2
multiplier:multiplier
constant:c];
// apparently only added in 10.9
if ([constraint respondsToSelector:@selector(setIdentifier:)])
[((id) constraint) setIdentifier:desc];
FUTURE_NSLayoutConstraint_setIdentifier(constraint, desc);
return constraint;
}

View File

@ -428,7 +428,7 @@ void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
// LONGTERM on other platforms
// or at leat allow this and implicitly turn it into a spacer
if (c == NULL)
userbug("You cannot add NULL to a uiBox.");
uiprivUserBug("You cannot add NULL to a uiBox.");
[b->view append:c stretchy:stretchy];
}

View File

@ -3,7 +3,7 @@
// LONGTERM don't halt on release builds
void realbug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap)
void uiprivRealBug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap)
{
NSMutableString *str;
NSString *formatted;

6
darwin/draw.h Normal file
View File

@ -0,0 +1,6 @@
// 6 january 2017
struct uiDrawContext {
CGContextRef c;
CGFloat height; // needed for text; see below
};

View File

@ -1,5 +1,6 @@
// 6 september 2015
#import "uipriv_darwin.h"
#import "draw.h"
struct uiDrawPath {
CGMutablePathRef path;
@ -11,7 +12,7 @@ uiDrawPath *uiDrawNewPath(uiDrawFillMode mode)
{
uiDrawPath *p;
p = uiNew(uiDrawPath);
p = uiprivNew(uiDrawPath);
p->path = CGPathCreateMutable();
p->fillMode = mode;
return p;
@ -20,13 +21,13 @@ uiDrawPath *uiDrawNewPath(uiDrawFillMode mode)
void uiDrawFreePath(uiDrawPath *p)
{
CGPathRelease((CGPathRef) (p->path));
uiFree(p);
uiprivFree(p);
}
void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
{
if (p->ended)
userbug("You cannot call uiDrawPathNewFigure() on a uiDrawPath that has already been ended. (path; %p)", p);
uiprivUserBug("You cannot call uiDrawPathNewFigure() on a uiDrawPath that has already been ended. (path; %p)", p);
CGPathMoveToPoint(p->path, NULL, x, y);
}
@ -36,7 +37,7 @@ void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, d
double startx, starty;
if (p->ended)
userbug("You cannot call uiDrawPathNewFigureWithArc() on a uiDrawPath that has already been ended. (path; %p)", p);
uiprivUserBug("You cannot call uiDrawPathNewFigureWithArc() on a uiDrawPath that has already been ended. (path; %p)", p);
sinStart = sin(startAngle);
cosStart = cos(startAngle);
startx = xCenter + radius * cosStart;
@ -49,7 +50,7 @@ void uiDrawPathLineTo(uiDrawPath *p, double x, double y)
{
// TODO refine this to require being in a path
if (p->ended)
implbug("attempt to add line to ended path in uiDrawPathLineTo()");
uiprivImplBug("attempt to add line to ended path in uiDrawPathLineTo()");
CGPathAddLineToPoint(p->path, NULL, x, y);
}
@ -59,7 +60,7 @@ void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radiu
// TODO likewise
if (p->ended)
implbug("attempt to add arc to ended path in uiDrawPathArcTo()");
uiprivImplBug("attempt to add arc to ended path in uiDrawPathArcTo()");
if (sweep > 2 * uiPi)
sweep = 2 * uiPi;
cw = false;
@ -76,7 +77,7 @@ void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, doubl
{
// TODO likewise
if (p->ended)
implbug("attempt to add bezier to ended path in uiDrawPathBezierTo()");
uiprivImplBug("attempt to add bezier to ended path in uiDrawPathBezierTo()");
CGPathAddCurveToPoint(p->path, NULL,
c1x, c1y,
c2x, c2y,
@ -87,14 +88,14 @@ void uiDrawPathCloseFigure(uiDrawPath *p)
{
// TODO likewise
if (p->ended)
implbug("attempt to close figure of ended path in uiDrawPathCloseFigure()");
uiprivImplBug("attempt to close figure of ended path in uiDrawPathCloseFigure()");
CGPathCloseSubpath(p->path);
}
void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)
{
if (p->ended)
userbug("You cannot call uiDrawPathAddRectangle() on a uiDrawPath that has already been ended. (path; %p)", p);
uiprivUserBug("You cannot call uiDrawPathAddRectangle() on a uiDrawPath that has already been ended. (path; %p)", p);
CGPathAddRect(p->path, NULL, CGRectMake(x, y, width, height));
}
@ -103,16 +104,11 @@ void uiDrawPathEnd(uiDrawPath *p)
p->ended = TRUE;
}
struct uiDrawContext {
CGContextRef c;
CGFloat height; // needed for text; see below
};
uiDrawContext *newContext(CGContextRef ctxt, CGFloat height)
{
uiDrawContext *c;
c = uiNew(uiDrawContext);
c = uiprivNew(uiDrawContext);
c->c = ctxt;
c->height = height;
return c;
@ -120,7 +116,7 @@ uiDrawContext *newContext(CGContextRef ctxt, CGFloat height)
void freeContext(uiDrawContext *c)
{
uiFree(c);
uiprivFree(c);
}
// a stroke is identical to a fill of a stroked path
@ -136,7 +132,7 @@ void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStro
uiDrawPath p2;
if (!path->ended)
userbug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
uiprivUserBug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
switch (p->Cap) {
case uiDrawLineCapFlat:
@ -164,7 +160,7 @@ void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStro
// create a temporary path identical to the previous one
dashPath = (CGPathRef) path->path;
if (p->NumDashes != 0) {
dashes = (CGFloat *) uiAlloc(p->NumDashes * sizeof (CGFloat), "CGFloat[]");
dashes = (CGFloat *) uiprivAlloc(p->NumDashes * sizeof (CGFloat), "CGFloat[]");
for (i = 0; i < p->NumDashes; i++)
dashes[i] = p->Dashes[i];
dashPath = CGPathCreateCopyByDashingPath(path->path,
@ -172,7 +168,7 @@ void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStro
p->DashPhase,
dashes,
p->NumDashes);
uiFree(dashes);
uiprivFree(dashes);
}
// the documentation is wrong: this produces a path suitable for calling CGPathCreateCopyByStrokingPath(), not for filling directly
// the cast is safe; we never modify the CGPathRef and always cast it back to a CGPathRef anyway
@ -222,10 +218,14 @@ static void fillGradient(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
// gradients need a color space
// for consistency with windows, use sRGB
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
if (colorspace == NULL) {
// TODO
}
// TODO add NULL check to other uses of CGColorSpace
// make the gradient
colors = uiAlloc(b->NumStops * 4 * sizeof (CGFloat), "CGFloat[]");
locations = uiAlloc(b->NumStops * sizeof (CGFloat), "CGFloat[]");
colors = uiprivAlloc(b->NumStops * 4 * sizeof (CGFloat), "CGFloat[]");
locations = uiprivAlloc(b->NumStops * sizeof (CGFloat), "CGFloat[]");
for (i = 0; i < b->NumStops; i++) {
colors[i * 4 + 0] = b->Stops[i].R;
colors[i * 4 + 1] = b->Stops[i].G;
@ -234,8 +234,8 @@ static void fillGradient(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
locations[i] = b->Stops[i].Pos;
}
gradient = CGGradientCreateWithColorComponents(colorspace, colors, locations, b->NumStops);
uiFree(locations);
uiFree(colors);
uiprivFree(locations);
uiprivFree(colors);
// because we're mucking with clipping, we need to save the graphics state and restore it later
CGContextSaveGState(ctxt);
@ -280,7 +280,7 @@ static void fillGradient(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)
{
if (!path->ended)
userbug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
uiprivUserBug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
CGContextAddPath(c->c, (CGPathRef) (path->path));
switch (b->Type) {
case uiDrawBrushTypeSolid:
@ -294,7 +294,7 @@ void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)
// TODO
return;
}
userbug("Unknown brush type %d passed to uiDrawFill().", b->Type);
uiprivUserBug("Unknown brush type %d passed to uiDrawFill().", b->Type);
}
static void m2c(uiDrawMatrix *m, CGAffineTransform *c)
@ -334,7 +334,7 @@ void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x
m2c(m, &c);
xt = x;
yt = y;
scaleCenter(xCenter, yCenter, &xt, &yt);
uiprivScaleCenter(xCenter, yCenter, &xt, &yt);
c = CGAffineTransformTranslate(c, xt, yt);
c = CGAffineTransformScale(c, x, y);
c = CGAffineTransformTranslate(c, -xt, -yt);
@ -354,7 +354,7 @@ void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)
void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
{
fallbackSkew(m, x, y, xamount, yamount);
uiprivFallbackSkew(m, x, y, xamount, yamount);
}
void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)
@ -425,7 +425,7 @@ void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)
void uiDrawClip(uiDrawContext *c, uiDrawPath *path)
{
if (!path->ended)
userbug("You cannot call uiDrawCilp() on a uiDrawPath that has not been ended. (path: %p)", path);
uiprivUserBug("You cannot call uiDrawCilp() on a uiDrawPath that has not been ended. (path: %p)", path);
CGContextAddPath(c->c, (CGPathRef) (path->path));
switch (path->fillMode) {
case uiDrawFillModeWinding:
@ -447,8 +447,3 @@ void uiDrawRestore(uiDrawContext *c)
{
CGContextRestoreGState(c->c);
}
void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
{
doDrawText(c->c, c->height, x, y, layout);
}

View File

@ -1,655 +1,214 @@
// 6 september 2015
// 7 march 2018
#import "uipriv_darwin.h"
#import "draw.h"
#import "attrstr.h"
// TODO
#define complain(...) implbug(__VA_ARGS__)
// TODO double-check that we are properly handling allocation failures (or just toll free bridge from cocoa)
struct uiDrawFontFamilies {
CFArrayRef fonts;
};
uiDrawFontFamilies *uiDrawListFontFamilies(void)
{
uiDrawFontFamilies *ff;
ff = uiNew(uiDrawFontFamilies);
ff->fonts = CTFontManagerCopyAvailableFontFamilyNames();
if (ff->fonts == NULL)
implbug("error getting available font names (no reason specified) (TODO)");
return ff;
// problem: for a CTFrame made from an empty string, the CTLine array will be empty, and we will crash when doing anything requiring a CTLine
// solution: for those cases, maintain a separate framesetter just for computing those things
// in the usual case, the separate copy will just be identical to the regular one, with extra references to everything within
@interface uiprivTextFrame : NSObject {
CFAttributedStringRef attrstr;
NSArray *backgroundParams;
CTFramesetterRef framesetter;
CGSize size;
CGPathRef path;
CTFrameRef frame;
}
- (id)initWithLayoutParams:(uiDrawTextLayoutParams *)p;
- (void)draw:(uiDrawContext *)c textLayout:(uiDrawTextLayout *)tl at:(double)x y:(double)y;
- (void)returnWidth:(double *)width height:(double *)height;
- (CFArrayRef)lines;
@end
int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
@implementation uiprivDrawTextBackgroundParams
- (id)initWithStart:(size_t)s end:(size_t)e r:(double)red g:(double)green b:(double)blue a:(double)alpha
{
return CFArrayGetCount(ff->fonts);
}
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)
{
CFStringRef familystr;
char *family;
familystr = (CFStringRef) CFArrayGetValueAtIndex(ff->fonts, n);
// toll-free bridge
family = uiDarwinNSStringToText((NSString *) familystr);
// Get Rule means we do not free familystr
return family;
}
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
{
CFRelease(ff->fonts);
uiFree(ff);
}
struct uiDrawTextFont {
CTFontRef f;
};
uiDrawTextFont *mkTextFont(CTFontRef f, BOOL retain)
{
uiDrawTextFont *font;
font = uiNew(uiDrawTextFont);
font->f = f;
if (retain)
CFRetain(font->f);
return font;
}
uiDrawTextFont *mkTextFontFromNSFont(NSFont *f)
{
// toll-free bridging; we do retain, though
return mkTextFont((CTFontRef) f, YES);
}
static CFMutableDictionaryRef newAttrList(void)
{
CFMutableDictionaryRef attr;
attr = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (attr == NULL)
complain("error creating attribute dictionary in newAttrList()()");
return attr;
}
static void addFontFamilyAttr(CFMutableDictionaryRef attr, const char *family)
{
CFStringRef cfstr;
cfstr = CFStringCreateWithCString(NULL, family, kCFStringEncodingUTF8);
if (cfstr == NULL)
complain("error creating font family name CFStringRef in addFontFamilyAttr()");
CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, cfstr);
CFRelease(cfstr); // dictionary holds its own reference
}
static void addFontSizeAttr(CFMutableDictionaryRef attr, double size)
{
CFNumberRef n;
n = CFNumberCreate(NULL, kCFNumberDoubleType, &size);
CFDictionaryAddValue(attr, kCTFontSizeAttribute, n);
CFRelease(n);
}
#if 0
TODO
// See http://stackoverflow.com/questions/4810409/does-coretext-support-small-caps/4811371#4811371 and https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c for what these do
// And fortunately, unlike the traits (see below), unmatched features are simply ignored without affecting the other features :D
static void addFontSmallCapsAttr(CFMutableDictionaryRef attr)
{
CFMutableArrayRef outerArray;
CFMutableDictionaryRef innerDict;
CFNumberRef numType, numSelector;
int num;
outerArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
if (outerArray == NULL)
complain("error creating outer CFArray for adding small caps attributes in addFontSmallCapsAttr()");
// Apple's headers say these are deprecated, but a few fonts still rely on them
num = kLetterCaseType;
numType = CFNumberCreate(NULL, kCFNumberIntType, &num);
num = kSmallCapsSelector;
numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);
innerDict = newAttrList();
CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);
CFRelease(numType);
CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);
CFRelease(numSelector);
CFArrayAppendValue(outerArray, innerDict);
CFRelease(innerDict); // and likewise for CFArray
// these are the non-deprecated versions of the above; some fonts have these instead
num = kLowerCaseType;
numType = CFNumberCreate(NULL, kCFNumberIntType, &num);
num = kLowerCaseSmallCapsSelector;
numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);
innerDict = newAttrList();
CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);
CFRelease(numType);
CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);
CFRelease(numSelector);
CFArrayAppendValue(outerArray, innerDict);
CFRelease(innerDict); // and likewise for CFArray
CFDictionaryAddValue(attr, kCTFontFeatureSettingsAttribute, outerArray);
CFRelease(outerArray);
}
#endif
// Named constants for these were NOT added until 10.11, and even then they were added as external symbols instead of macros, so we can't use them directly :(
// kode54 got these for me before I had access to El Capitan; thanks to him.
#define ourNSFontWeightUltraLight -0.800000
#define ourNSFontWeightThin -0.600000
#define ourNSFontWeightLight -0.400000
#define ourNSFontWeightRegular 0.000000
#define ourNSFontWeightMedium 0.230000
#define ourNSFontWeightSemibold 0.300000
#define ourNSFontWeightBold 0.400000
#define ourNSFontWeightHeavy 0.560000
#define ourNSFontWeightBlack 0.620000
static const CGFloat ctWeights[] = {
// yeah these two have their names swapped; blame Pango
[uiDrawTextWeightThin] = ourNSFontWeightUltraLight,
[uiDrawTextWeightUltraLight] = ourNSFontWeightThin,
[uiDrawTextWeightLight] = ourNSFontWeightLight,
// for this one let's go between Light and Regular
// we're doing nearest so if there happens to be an exact value hopefully it's close enough
[uiDrawTextWeightBook] = ourNSFontWeightLight + ((ourNSFontWeightRegular - ourNSFontWeightLight) / 2),
[uiDrawTextWeightNormal] = ourNSFontWeightRegular,
[uiDrawTextWeightMedium] = ourNSFontWeightMedium,
[uiDrawTextWeightSemiBold] = ourNSFontWeightSemibold,
[uiDrawTextWeightBold] = ourNSFontWeightBold,
// for this one let's go between Bold and Heavy
[uiDrawTextWeightUltraBold] = ourNSFontWeightBold + ((ourNSFontWeightHeavy - ourNSFontWeightBold) / 2),
[uiDrawTextWeightHeavy] = ourNSFontWeightHeavy,
[uiDrawTextWeightUltraHeavy] = ourNSFontWeightBlack,
};
// Unfortunately there are still no named constants for these.
// Let's just use normalized widths.
// As far as I can tell (OS X only ships with condensed fonts, not expanded fonts; TODO), regardless of condensed or expanded, negative means condensed and positive means expanded.
// TODO verify this is correct
static const CGFloat ctStretches[] = {
[uiDrawTextStretchUltraCondensed] = -1.0,
[uiDrawTextStretchExtraCondensed] = -0.75,
[uiDrawTextStretchCondensed] = -0.5,
[uiDrawTextStretchSemiCondensed] = -0.25,
[uiDrawTextStretchNormal] = 0.0,
[uiDrawTextStretchSemiExpanded] = 0.25,
[uiDrawTextStretchExpanded] = 0.5,
[uiDrawTextStretchExtraExpanded] = 0.75,
[uiDrawTextStretchUltraExpanded] = 1.0,
};
struct closeness {
CFIndex index;
CGFloat weight;
CGFloat italic;
CGFloat stretch;
CGFloat distance;
};
// Stupidity: CTFont requires an **exact match for the entire traits dictionary**, otherwise it will **drop ALL the traits**.
// We have to implement the closest match ourselves.
// Also we have to do this before adding the small caps flags, because the matching descriptors won't have those.
CTFontDescriptorRef matchTraits(CTFontDescriptorRef against, uiDrawTextWeight weight, uiDrawTextItalic italic, uiDrawTextStretch stretch)
{
CGFloat targetWeight;
CGFloat italicCloseness, obliqueCloseness, normalCloseness;
CGFloat targetStretch;
CFArrayRef matching;
CFIndex i, n;
struct closeness *closeness;
CTFontDescriptorRef current;
CTFontDescriptorRef out;
targetWeight = ctWeights[weight];
switch (italic) {
case uiDrawTextItalicNormal:
italicCloseness = 1;
obliqueCloseness = 1;
normalCloseness = 0;
break;
case uiDrawTextItalicOblique:
italicCloseness = 0.5;
obliqueCloseness = 0;
normalCloseness = 1;
break;
case uiDrawTextItalicItalic:
italicCloseness = 0;
obliqueCloseness = 0.5;
normalCloseness = 1;
break;
self = [super init];
if (self) {
self->start = s;
self->end = e;
self->r = red;
self->g = green;
self->b = blue;
self->a = alpha;
}
targetStretch = ctStretches[stretch];
matching = CTFontDescriptorCreateMatchingFontDescriptors(against, NULL);
if (matching == NULL)
// no matches; give the original back and hope for the best
return against;
n = CFArrayGetCount(matching);
if (n == 0) {
// likewise
CFRelease(matching);
return against;
}
closeness = (struct closeness *) uiAlloc(n * sizeof (struct closeness), "struct closeness[]");
for (i = 0; i < n; i++) {
CFDictionaryRef traits;
CFNumberRef cfnum;
CTFontSymbolicTraits symbolic;
closeness[i].index = i;
current = CFArrayGetValueAtIndex(matching, i);
traits = CTFontDescriptorCopyAttribute(current, kCTFontTraitsAttribute);
if (traits == NULL) {
// couldn't get traits; be safe by ranking it lowest
// LONGTERM figure out what the longest possible distances are
closeness[i].weight = 3;
closeness[i].italic = 2;
closeness[i].stretch = 3;
continue;
}
symbolic = 0; // assume no symbolic traits if none are listed
cfnum = CFDictionaryGetValue(traits, kCTFontSymbolicTrait);
if (cfnum != NULL) {
SInt32 s;
if (CFNumberGetValue(cfnum, kCFNumberSInt32Type, &s) == false)
complain("error getting symbolic traits in matchTraits()");
symbolic = (CTFontSymbolicTraits) s;
// Get rule; do not release cfnum
}
// now try weight
cfnum = CFDictionaryGetValue(traits, kCTFontWeightTrait);
if (cfnum != NULL) {
CGFloat val;
// LONGTERM instead of complaining for this and width and possibly also symbolic traits above, should we just fall through to the default?
if (CFNumberGetValue(cfnum, kCFNumberCGFloatType, &val) == false)
complain("error getting weight value in matchTraits()");
closeness[i].weight = val - targetWeight;
} else
// okay there's no weight key; let's try the literal meaning of the symbolic constant
// LONGTERM is the weight key guaranteed?
if ((symbolic & kCTFontBoldTrait) != 0)
closeness[i].weight = ourNSFontWeightBold - targetWeight;
else
closeness[i].weight = ourNSFontWeightRegular - targetWeight;
// italics is a bit harder because Core Text doesn't expose a concept of obliqueness
// Pango just does a g_strrstr() (backwards case-sensitive search) for "Oblique" in the font's style name (see https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c); let's do that too I guess
if ((symbolic & kCTFontItalicTrait) != 0)
closeness[i].italic = italicCloseness;
else {
CFStringRef styleName;
BOOL isOblique;
isOblique = NO; // default value
styleName = CTFontDescriptorCopyAttribute(current, kCTFontStyleNameAttribute);
if (styleName != NULL) {
CFRange range;
// note the use of the toll-free bridge for the string literal, since CFSTR() *can* return NULL
range = CFStringFind(styleName, (CFStringRef) @"Oblique", kCFCompareBackwards);
if (range.location != kCFNotFound)
isOblique = YES;
CFRelease(styleName);
}
if (isOblique)
closeness[i].italic = obliqueCloseness;
else
closeness[i].italic = normalCloseness;
}
// now try width
// TODO this does not seem to be enough for Skia's extended variants; the width trait is 0 but the Expanded flag is on
// TODO verify the rest of this matrix (what matrix?)
cfnum = CFDictionaryGetValue(traits, kCTFontWidthTrait);
if (cfnum != NULL) {
CGFloat val;
if (CFNumberGetValue(cfnum, kCFNumberCGFloatType, &val) == false)
complain("error getting width value in matchTraits()");
closeness[i].stretch = val - targetStretch;
} else
// okay there's no width key; let's try the literal meaning of the symbolic constant
// LONGTERM is the width key guaranteed?
if ((symbolic & kCTFontExpandedTrait) != 0)
closeness[i].stretch = 1.0 - targetStretch;
else if ((symbolic & kCTFontCondensedTrait) != 0)
closeness[i].stretch = -1.0 - targetStretch;
else
closeness[i].stretch = 0.0 - targetStretch;
CFRelease(traits);
}
// now figure out the 3-space difference between the three and sort by that
for (i = 0; i < n; i++) {
CGFloat weight, italic, stretch;
weight = closeness[i].weight;
weight *= weight;
italic = closeness[i].italic;
italic *= italic;
stretch = closeness[i].stretch;
stretch *= stretch;
closeness[i].distance = sqrt(weight + italic + stretch);
}
qsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) {
const struct closeness *a = (const struct closeness *) aa;
const struct closeness *b = (const struct closeness *) bb;
// via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions
// LONGTERM is this really the best way? isn't it the same as if (*a < *b) return -1; if (*a > *b) return 1; return 0; ?
return (a->distance > b->distance) - (a->distance < b->distance);
});
// and the first element of the sorted array is what we want
out = CFArrayGetValueAtIndex(matching, closeness[0].index);
CFRetain(out); // get rule
// release everything
uiFree(closeness);
CFRelease(matching);
// and release the original descriptor since we no longer need it
CFRelease(against);
return out;
return self;
}
// Now remember what I said earlier about having to add the small caps traits after calling the above? This gets a dictionary back so we can do so.
CFMutableDictionaryRef extractAttributes(CTFontDescriptorRef desc)
{
CFDictionaryRef dict;
CFMutableDictionaryRef mdict;
dict = CTFontDescriptorCopyAttributes(desc);
// this might not be mutable, so make a mutable copy
mdict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
CFRelease(dict);
return mdict;
}
uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
{
CTFontRef f;
CFMutableDictionaryRef attr;
CTFontDescriptorRef cfdesc;
attr = newAttrList();
addFontFamilyAttr(attr, desc->Family);
addFontSizeAttr(attr, desc->Size);
// now we have to do the traits matching, so create a descriptor, match the traits, and then get the attributes back
cfdesc = CTFontDescriptorCreateWithAttributes(attr);
// TODO release attr?
cfdesc = matchTraits(cfdesc, desc->Weight, desc->Italic, desc->Stretch);
// specify the initial size again just to be safe
f = CTFontCreateWithFontDescriptor(cfdesc, desc->Size, NULL);
// TODO release cfdesc?
return mkTextFont(f, NO); // we hold the initial reference; no need to retain again
}
void uiDrawFreeTextFont(uiDrawTextFont *font)
{
CFRelease(font->f);
uiFree(font);
}
uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
{
return (uintptr_t) (font->f);
}
void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
- (void)draw:(CGContextRef)c layout:(uiDrawTextLayout *)layout at:(double)x y:(double)y utf8Mapping:(const size_t *)u16tou8
{
// TODO
}
// text sizes and user space points are identical:
// - https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TypoFeatures/TextSystemFeatures.html#//apple_ref/doc/uid/TP40009459-CH6-51627-BBCCHIFF text points are 72 per inch
// - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Transforms/Transforms.html#//apple_ref/doc/uid/TP40003290-CH204-SW5 user space points are 72 per inch
void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
@end
@implementation uiprivTextFrame
- (id)initWithLayoutParams:(uiDrawTextLayoutParams *)p
{
metrics->Ascent = CTFontGetAscent(font->f);
metrics->Descent = CTFontGetDescent(font->f);
metrics->Leading = CTFontGetLeading(font->f);
metrics->UnderlinePos = CTFontGetUnderlinePosition(font->f);
metrics->UnderlineThickness = CTFontGetUnderlineThickness(font->f);
}
CFRange range;
CGFloat cgwidth;
CFRange unused;
CGRect rect;
struct uiDrawTextLayout {
CFMutableAttributedStringRef mas;
CFRange *charsToRanges;
double width;
};
self = [super init];
if (self) {
self->attrstr = uiprivAttributedStringToCFAttributedString(p, &(self->backgroundParams));
// TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing
self->framesetter = CTFramesetterCreateWithAttributedString(self->attrstr);
if (self->framesetter == NULL) {
// TODO
}
uiDrawTextLayout *uiDrawNewTextLayout(const char *str, uiDrawTextFont *defaultFont, double width)
{
uiDrawTextLayout *layout;
CFAttributedStringRef immutable;
CFMutableDictionaryRef attr;
CFStringRef backing;
CFIndex i, j, n;
range.location = 0;
range.length = CFAttributedStringGetLength(self->attrstr);
layout = uiNew(uiDrawTextLayout);
cgwidth = (CGFloat) (p->Width);
if (cgwidth < 0)
cgwidth = CGFLOAT_MAX;
self->size = CTFramesetterSuggestFrameSizeWithConstraints(self->framesetter,
range,
// TODO kCTFramePathWidthAttributeName?
NULL,
CGSizeMake(cgwidth, CGFLOAT_MAX),
&unused); // not documented as accepting NULL (TODO really?)
// TODO docs say we need to use a different set of key callbacks
// TODO see if the font attribute key callbacks need to be the same
attr = newAttrList();
// this will retain defaultFont->f; no need to worry
CFDictionaryAddValue(attr, kCTFontAttributeName, defaultFont->f);
immutable = CFAttributedStringCreate(NULL, (CFStringRef) [NSString stringWithUTF8String:str], attr);
if (immutable == NULL)
complain("error creating immutable attributed string in uiDrawNewTextLayout()");
CFRelease(attr);
layout->mas = CFAttributedStringCreateMutableCopy(NULL, 0, immutable);
if (layout->mas == NULL)
complain("error creating attributed string in uiDrawNewTextLayout()");
CFRelease(immutable);
uiDrawTextLayoutSetWidth(layout, width);
// unfortunately the CFRanges for attributes expect UTF-16 codepoints
// we want graphemes
// fortunately CFStringGetRangeOfComposedCharactersAtIndex() is here for us
// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Strings/Articles/stringsClusters.html says that this does work on all multi-codepoint graphemes (despite the name), and that this is the preferred function for this particular job anyway
backing = CFAttributedStringGetString(layout->mas);
n = CFStringGetLength(backing);
// allocate one extra, just to be safe
layout->charsToRanges = (CFRange *) uiAlloc((n + 1) * sizeof (CFRange), "CFRange[]");
i = 0;
j = 0;
while (i < n) {
CFRange range;
range = CFStringGetRangeOfComposedCharactersAtIndex(backing, i);
i = range.location + range.length;
layout->charsToRanges[j] = range;
j++;
rect.origin = CGPointZero;
rect.size = self->size;
self->path = CGPathCreateWithRect(rect, NULL);
self->frame = CTFramesetterCreateFrame(self->framesetter,
range,
self->path,
// TODO kCTFramePathWidthAttributeName?
NULL);
if (self->frame == NULL) {
// TODO
}
}
// and set the last one
layout->charsToRanges[j].location = i;
layout->charsToRanges[j].length = 0;
return layout;
return self;
}
void uiDrawFreeTextLayout(uiDrawTextLayout *layout)
- (void)dealloc
{
uiFree(layout->charsToRanges);
CFRelease(layout->mas);
uiFree(layout);
CFRelease(self->frame);
CFRelease(self->path);
CFRelease(self->framesetter);
[self->backgroundParams release];
CFRelease(self->attrstr);
[super dealloc];
}
void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width)
- (void)draw:(uiDrawContext *)c textLayout:(uiDrawTextLayout *)tl at:(double)x y:(double)y
{
layout->width = width;
}
uiprivDrawTextBackgroundParams *dtb;
CGAffineTransform textMatrix;
struct framesetter {
CTFramesetterRef fs;
CFMutableDictionaryRef frameAttrib;
CGSize extents;
};
CGContextSaveGState(c->c);
// save the text matrix because it's not part of the graphics state
textMatrix = CGContextGetTextMatrix(c->c);
// TODO CTFrameProgression for RTL/LTR
// TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing
static void mkFramesetter(uiDrawTextLayout *layout, struct framesetter *fs)
{
CFRange fitRange;
CGFloat width;
for (dtb in self->backgroundParams)
/* TODO */;
fs->fs = CTFramesetterCreateWithAttributedString(layout->mas);
if (fs->fs == NULL)
complain("error creating CTFramesetter object in mkFramesetter()");
// TODO kCTFramePathWidthAttributeName?
fs->frameAttrib = NULL;
width = layout->width;
if (layout->width < 0)
width = CGFLOAT_MAX;
// TODO these seem to be floor()'d or truncated?
fs->extents = CTFramesetterSuggestFrameSizeWithConstraints(fs->fs,
CFRangeMake(0, 0),
fs->frameAttrib,
CGSizeMake(width, CGFLOAT_MAX),
&fitRange); // not documented as accepting NULL
}
static void freeFramesetter(struct framesetter *fs)
{
if (fs->frameAttrib != NULL)
CFRelease(fs->frameAttrib);
CFRelease(fs->fs);
}
// LONGTERM allow line separation and leading to be factored into a wrapping text layout
// TODO reconcile differences in character wrapping on platforms
void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height)
{
struct framesetter fs;
mkFramesetter(layout, &fs);
*width = fs.extents.width;
*height = fs.extents.height;
freeFramesetter(&fs);
}
// Core Text doesn't draw onto a flipped view correctly; we have to do this
// see the iOS bits of the first example at https://developer.apple.com/library/mac/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html#//apple_ref/doc/uid/TP40005533-CH12-SW1 (iOS is naturally flipped)
// TODO how is this affected by the CTM?
static void prepareContextForText(CGContextRef c, CGFloat cheight, double *y)
{
CGContextSaveGState(c);
CGContextTranslateCTM(c, 0, cheight);
CGContextScaleCTM(c, 1.0, -1.0);
CGContextSetTextMatrix(c, CGAffineTransformIdentity);
// Core Text doesn't draw onto a flipped view correctly; we have to pretend it was unflipped
// see the iOS bits of the first example at https://developer.apple.com/library/mac/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html#//apple_ref/doc/uid/TP40005533-CH12-SW1 (iOS is naturally flipped)
// TODO how is this affected by a non-identity CTM?
CGContextTranslateCTM(c->c, 0, c->height);
CGContextScaleCTM(c->c, 1.0, -1.0);
CGContextSetTextMatrix(c->c, CGAffineTransformIdentity);
// wait, that's not enough; we need to offset y values to account for our new flipping
*y = cheight - *y;
// TODO explain this calculation
y = c->height - self->size.height - y;
// CTFrameDraw() draws in the path we specified when creating the frame
// this means that in our usage, CTFrameDraw() will draw at (0,0)
// so move the origin to be at (x,y) instead
// TODO are the signs correct?
CGContextTranslateCTM(c->c, x, y);
CTFrameDraw(self->frame, c->c);
CGContextSetTextMatrix(c->c, textMatrix);
CGContextRestoreGState(c->c);
}
// TODO placement is incorrect for Helvetica
void doDrawText(CGContextRef c, CGFloat cheight, double x, double y, uiDrawTextLayout *layout)
- (void)returnWidth:(double *)width height:(double *)height
{
struct framesetter fs;
CGRect rect;
CGPathRef path;
CTFrameRef frame;
prepareContextForText(c, cheight, &y);
mkFramesetter(layout, &fs);
// oh, and since we're flipped, y is the bottom-left coordinate of the rectangle, not the top-left
// since we are flipped, we subtract
y -= fs.extents.height;
rect.origin = CGPointMake(x, y);
rect.size = fs.extents;
path = CGPathCreateWithRect(rect, NULL);
frame = CTFramesetterCreateFrame(fs.fs,
CFRangeMake(0, 0),
path,
fs.frameAttrib);
if (frame == NULL)
complain("error creating CTFrame object in doDrawText()");
CTFrameDraw(frame, c);
CFRelease(frame);
CFRelease(path);
freeFramesetter(&fs);
CGContextRestoreGState(c);
if (width != NULL)
*width = self->size.width;
if (height != NULL)
*height = self->size.height;
}
// LONGTERM provide an equivalent to CTLineGetTypographicBounds() on uiDrawTextLayout?
// LONGTERM keep this for later features and documentation purposes
#if 0
w = CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
// though CTLineGetTypographicBounds() returns 0 on error, it also returns 0 on an empty string, so we can't reasonably check for error
CFRelease(line);
// LONGTERM provide a way to get the image bounds as a separate function later
bounds = CTLineGetImageBounds(line, c);
// though CTLineGetImageBounds() returns CGRectNull on error, it also returns CGRectNull on an empty string, so we can't reasonably check for error
// CGContextSetTextPosition() positions at the baseline in the case of CTLineDraw(); we need the top-left corner instead
CTLineGetTypographicBounds(line, &yoff, NULL, NULL);
// remember that we're flipped, so we subtract
y -= yoff;
CGContextSetTextPosition(c, x, y);
#endif
static CFRange charsToRange(uiDrawTextLayout *layout, int startChar, int endChar)
- (CFArrayRef)lines
{
CFRange start, end;
CFRange out;
start = layout->charsToRanges[startChar];
end = layout->charsToRanges[endChar];
out.location = start.location;
out.length = end.location - start.location;
return out;
return CTFrameGetLines(self->frame);
}
#define rangeToCFRange() charsToRange(layout, startChar, endChar)
@end
void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)
struct uiDrawTextLayout {
uiprivTextFrame *frame;
uiprivTextFrame *forLines;
BOOL empty;
// for converting CFAttributedString indices from/to byte offsets
size_t *u8tou16;
size_t nUTF8;
size_t *u16tou8;
size_t nUTF16;
};
uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)
{
CGColorSpaceRef colorspace;
CGFloat components[4];
CGColorRef color;
uiDrawTextLayout *tl;
// for consistency with windows, use sRGB
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
components[0] = r;
components[1] = g;
components[2] = b;
components[3] = a;
color = CGColorCreate(colorspace, components);
CGColorSpaceRelease(colorspace);
tl = uiprivNew(uiDrawTextLayout);
tl->frame = [[uiprivTextFrame alloc] initWithLayoutParams:p];
if (uiAttributedStringLen(p->String) != 0)
tl->forLines = [tl->frame retain];
else {
uiAttributedString *space;
uiDrawTextLayoutParams p2;
CFAttributedStringSetAttribute(layout->mas,
rangeToCFRange(),
kCTForegroundColorAttributeName,
color);
CGColorRelease(color); // TODO safe?
tl->empty = YES;
space = uiNewAttributedString(" ");
p2 = *p;
p2.String = space;
tl->forLines = [[uiprivTextFrame alloc] initWithLayoutParams:&p2];
uiFreeAttributedString(space);
}
// and finally copy the UTF-8/UTF-16 conversion tables
tl->u8tou16 = uiprivAttributedStringCopyUTF8ToUTF16Table(p->String, &(tl->nUTF8));
tl->u16tou8 = uiprivAttributedStringCopyUTF16ToUTF8Table(p->String, &(tl->nUTF16));
return tl;
}
void uiDrawFreeTextLayout(uiDrawTextLayout *tl)
{
uiprivFree(tl->u16tou8);
uiprivFree(tl->u8tou16);
[tl->forLines release];
[tl->frame release];
uiprivFree(tl);
}
// TODO document that (x,y) is the top-left corner of the *entire frame*
void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
{
[tl->frame draw:c textLayout:tl at:x y:y];
}
// TODO document that the width and height of a layout is not necessarily the sum of the widths and heights of its constituent lines
// TODO width doesn't include trailing whitespace...
// TODO figure out how paragraph spacing should play into this
// TODO standardize and document the behavior of this on an empty layout
void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
{
// TODO explain this, given the above
[tl->frame returnWidth:width height:NULL];
[tl->forLines returnWidth:NULL height:height];
}

View File

@ -1,7 +1,8 @@
// 14 april 2016
#import "uipriv_darwin.h"
#import "attrstr.h"
@interface fontButton : NSButton {
@interface uiprivFontButton : NSButton {
uiFontButton *libui_b;
NSFont *libui_font;
}
@ -11,20 +12,20 @@
- (void)activateFontButton;
- (void)deactivateFontButton:(BOOL)activatingAnother;
- (void)deactivateOnClose:(NSNotification *)note;
- (uiDrawTextFont *)libuiFont;
- (void)getfontdesc:(uiFontDescriptor *)uidesc;
@end
// only one may be active at one time
static fontButton *activeFontButton = nil;
static uiprivFontButton *activeFontButton = nil;
struct uiFontButton {
uiDarwinControl c;
fontButton *button;
uiprivFontButton *button;
void (*onChanged)(uiFontButton *, void *);
void *onChangedData;
};
@implementation fontButton
@implementation uiprivFontButton
- (id)initWithFrame:(NSRect)frame libuiFontButton:(uiFontButton *)b
{
@ -138,9 +139,16 @@ struct uiFontButton {
NSFontPanelCollectionModeMask;
}
- (uiDrawTextFont *)libuiFont
- (void)getfontdesc:(uiFontDescriptor *)uidesc
{
return mkTextFontFromNSFont(self->libui_font);
CTFontRef ctfont;
CTFontDescriptorRef ctdesc;
ctfont = (CTFontRef) (self->libui_font);
ctdesc = CTFontCopyFontDescriptor(ctfont);
uiprivFontDescriptorFromCTFontDescriptor(ctdesc, uidesc);
CFRelease(ctdesc);
uidesc->Size = CTFontGetSize(ctfont);
}
@end
@ -149,16 +157,16 @@ uiDarwinControlAllDefaults(uiFontButton, button)
// we do not want font change events to be sent to any controls other than the font buttons
// see main.m for more details
BOOL fontButtonInhibitSendAction(SEL sel, id from, id to)
BOOL uiprivFontButtonInhibitSendAction(SEL sel, id from, id to)
{
if (sel != @selector(changeFont:))
return NO;
return ![to isKindOfClass:[fontButton class]];
return ![to isKindOfClass:[uiprivFontButton class]];
}
// we do not want NSFontPanelValidation messages to be sent to any controls other than the font buttons when a font button is active
// see main.m for more details
BOOL fontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override)
BOOL uiprivFontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override)
{
if (activeFontButton == nil)
return NO;
@ -170,10 +178,10 @@ BOOL fontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override)
// we also don't want the panel to be usable when there's a dialog running; see stddialogs.m for more details on that
// unfortunately the panel seems to ignore -setWorksWhenModal: so we'll have to do things ourselves
@interface nonModalFontPanel : NSFontPanel
@interface uiprivNonModalFontPanel : NSFontPanel
@end
@implementation nonModalFontPanel
@implementation uiprivNonModalFontPanel
- (BOOL)worksWhenModal
{
@ -182,9 +190,9 @@ BOOL fontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override)
@end
void setupFontPanel(void)
void uiprivSetupFontPanel(void)
{
[NSFontManager setFontPanelFactory:[nonModalFontPanel class]];
[NSFontManager setFontPanelFactory:[uiprivNonModalFontPanel class]];
}
static void defaultOnChanged(uiFontButton *b, void *data)
@ -192,9 +200,9 @@ static void defaultOnChanged(uiFontButton *b, void *data)
// do nothing
}
uiDrawTextFont *uiFontButtonFont(uiFontButton *b)
void uiFontButtonFont(uiFontButton *b, uiFontDescriptor *desc)
{
return [b->button libuiFont];
[b->button getfontdesc:desc];
}
void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data)
@ -209,10 +217,16 @@ uiFontButton *uiNewFontButton(void)
uiDarwinNewControl(uiFontButton, b);
b->button = [[fontButton alloc] initWithFrame:NSZeroRect libuiFontButton:b];
b->button = [[uiprivFontButton alloc] initWithFrame:NSZeroRect libuiFontButton:b];
uiDarwinSetControlFont(b->button, NSRegularControlSize);
uiFontButtonOnChanged(b, defaultOnChanged, NULL);
return b;
}
void uiFreeFontButtonFont(uiFontDescriptor *desc)
{
// TODO ensure this is synchronized with fontmatch.m
uiFreeText((char *) (desc->Family));
}

494
darwin/fontmatch.m Normal file
View File

@ -0,0 +1,494 @@
// 3 january 2017
#import "uipriv_darwin.h"
#import "attrstr.h"
// TODOs:
// - switching from Skia to a non-fvar-based font crashes because the CTFontDescriptorRef we get has an empty variation dictionary for some reason...
// - Futura causes the Courier New in the drawtext example to be bold for some reason...
// Core Text exposes font style info in two forms:
// - Fonts with a QuickDraw GX font variation (fvar) table, a feature
// adopted by OpenType, expose variations directly.
// - All other fonts have Core Text normalize the font style info
// into a traits dictionary.
// Of course this setup doesn't come without its hiccups and
// glitches. In particular, not only are the exact rules not well
// defined, but also font matching doesn't work as we want it to
// (exactly how varies based on the way the style info is exposed).
// So we'll have to implement style matching ourselves.
// We can use Core Text's matching to get a complete list of
// *possible* matches, and then we can filter out the ones we don't
// want ourselves.
//
// To make things easier for us, we'll match by converting Core
// Text's values back into libui values. This allows us to also use the
// normalization code for filling in uiFontDescriptors from
// Core Text fonts and font descriptors.
//
// Style matching needs to be done early in the font loading process;
// in particular, we have to do this before adding any features,
// because the descriptors returned by Core Text's own font
// matching won't have any.
@implementation uiprivFontStyleData
- (id)initWithFont:(CTFontRef)f
{
self = [super init];
if (self) {
self->font = f;
CFRetain(self->font);
self->desc = CTFontCopyFontDescriptor(self->font);
if (![self prepare]) {
[self release];
return nil;
}
}
return self;
}
- (id)initWithDescriptor:(CTFontDescriptorRef)d
{
self = [super init];
if (self) {
self->font = NULL;
self->desc = d;
CFRetain(self->desc);
if (![self prepare]) {
[self release];
return nil;
}
}
return self;
}
- (void)dealloc
{
#define REL(x) if (x != NULL) { CFRelease(x); x = NULL; }
REL(self->variationAxes);
REL(self->familyName);
REL(self->preferredFamilyName);
REL(self->fullName);
REL(self->subFamilyName);
REL(self->preferredSubFamilyName);
REL(self->postScriptName);
REL(self->variation);
REL(self->styleName);
REL(self->traits);
CFRelease(self->desc);
REL(self->font);
[super dealloc];
}
- (BOOL)prepare
{
CFNumberRef num;
Boolean success;
self->traits = NULL;
self->symbolic = 0;
self->weight = 0;
self->width = 0;
self->didStyleName = NO;
self->styleName = NULL;
self->didVariation = NO;
self->variation = NULL;
self->hasRegistrationScope = NO;
self->registrationScope = 0;
self->didPostScriptName = NO;
self->postScriptName = NULL;
self->fontFormat = 0;
self->didPreferredSubFamilyName = NO;
self->preferredSubFamilyName = NULL;
self->didSubFamilyName = NO;
self->subFamilyName = NULL;
self->didFullName = NO;
self->fullName = NULL;
self->didPreferredFamilyName = NO;
self->preferredFamilyName = NULL;
self->didFamilyName = NO;
self->familyName = NULL;
self->didVariationAxes = NO;
self->variationAxes = NULL;
self->traits = (CFDictionaryRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontTraitsAttribute);
if (self->traits == NULL)
return NO;
num = (CFNumberRef) CFDictionaryGetValue(self->traits, kCTFontSymbolicTrait);
if (num == NULL)
return NO;
if (CFNumberGetValue(num, kCFNumberSInt32Type, &(self->symbolic)) == false)
return NO;
num = (CFNumberRef) CFDictionaryGetValue(self->traits, kCTFontWeightTrait);
if (num == NULL)
return NO;
if (CFNumberGetValue(num, kCFNumberDoubleType, &(self->weight)) == false)
return NO;
num = (CFNumberRef) CFDictionaryGetValue(self->traits, kCTFontWidthTrait);
if (num == NULL)
return NO;
if (CFNumberGetValue(num, kCFNumberDoubleType, &(self->width)) == false)
return NO;
// do these now for the sake of error checking
num = (CFNumberRef) CTFontDescriptorCopyAttribute(desc, kCTFontRegistrationScopeAttribute);
self->hasRegistrationScope = num != NULL;
if (self->hasRegistrationScope) {
success = CFNumberGetValue(num, kCFNumberSInt32Type, &(self->registrationScope));
CFRelease(num);
if (success == false)
return NO;
}
num = (CFNumberRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontFormatAttribute);
if (num == NULL)
return NO;
success = CFNumberGetValue(num, kCFNumberSInt32Type, &(self->fontFormat));
CFRelease(num);
if (success == false)
return NO;
return YES;
}
- (void)ensureFont
{
if (self->font != NULL)
return;
self->font = CTFontCreateWithFontDescriptor(self->desc, 0.0, NULL);
}
- (CTFontSymbolicTraits)symbolicTraits
{
return self->symbolic;
}
- (double)weight
{
return self->weight;
}
- (double)width
{
return self->width;
}
- (CFStringRef)styleName
{
if (!self->didStyleName) {
self->didStyleName = YES;
self->styleName = (CFStringRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontStyleNameAttribute);
// The code we use this for (guessItalicOblique() below) checks if this is NULL or not, so we're good.
}
return self->styleName;
}
- (CFDictionaryRef)variation
{
if (!self->didVariation) {
self->didVariation = YES;
self->variation = (CFDictionaryRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontVariationAttribute);
// This being NULL is used to determine whether a font uses variations at all, so we don't need to worry now.
}
return self->variation;
}
- (BOOL)hasRegistrationScope
{
return self->hasRegistrationScope;
}
- (CTFontManagerScope)registrationScope
{
return self->registrationScope;
}
- (CFStringRef)postScriptName
{
if (!self->didPostScriptName) {
self->didPostScriptName = YES;
[self ensureFont];
self->postScriptName = CTFontCopyPostScriptName(self->font);
}
return self->postScriptName;
}
- (CFDataRef)table:(CTFontTableTag)tag
{
[self ensureFont];
return CTFontCopyTable(self->font, tag, kCTFontTableOptionNoOptions);
}
- (CTFontFormat)fontFormat
{
return self->fontFormat;
}
// We don't need to worry if this or any of the functions that use it return NULL, because the code that uses it (libFontRegistry.dylib bug workarounds in fonttraits.m) checks for NULL.
- (CFStringRef)fontName:(CFStringRef)key
{
[self ensureFont];
return CTFontCopyName(self->font, key);
}
#define FONTNAME(sel, did, var, key) \
- (CFStringRef)sel \
{ \
if (!did) { \
did = YES; \
var = [self fontName:key]; \
} \
return var; \
}
FONTNAME(preferredSubFamilyName,
self->didPreferredSubFamilyName,
self->preferredSubFamilyName,
UNDOC_kCTFontPreferredSubFamilyNameKey)
FONTNAME(subFamilyName,
self->didSubFamilyName,
self->subFamilyName,
kCTFontSubFamilyNameKey)
FONTNAME(fullName,
self->didFullName,
self->fullName,
kCTFontFullNameKey)
FONTNAME(preferredFamilyName,
self->didPreferredFamilyName,
self->preferredFamilyName,
UNDOC_kCTFontPreferredFamilyNameKey)
FONTNAME(familyName,
self->didFamilyName,
self->familyName,
kCTFontFamilyNameKey)
- (CFArrayRef)variationAxes
{
if (!self->didVariationAxes) {
self->didVariationAxes = YES;
[self ensureFont];
self->variationAxes = CTFontCopyVariationAxes(self->font);
// We don't care about the return value because we call this only on fonts that we know have variations anyway.
}
return self->variationAxes;
}
@end
struct closeness {
CFIndex index;
uiTextWeight weight;
double italic;
uiTextStretch stretch;
double distance;
};
// remember that in closeness, 0 means exact
// in this case, since we define the range, we use 0.5 to mean "close enough" (oblique for italic and italic for oblique) and 1 to mean "not a match"
static const double italicClosenessNormal[] = { 0, 1, 1 };
static const double italicClosenessOblique[] = { 1, 0, 0.5 };
static const double italicClosenessItalic[] = { 1, 0.5, 0 };
static const double *italicClosenesses[] = {
[uiTextItalicNormal] = italicClosenessNormal,
[uiTextItalicOblique] = italicClosenessOblique,
[uiTextItalicItalic] = italicClosenessItalic,
};
// Core Text doesn't seem to differentiate between Italic and Oblique.
// Pango's Core Text code just does a g_strrstr() (backwards case-sensitive search) for "Oblique" in the font's style name (see https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c); let's do that too I guess
static uiTextItalic guessItalicOblique(uiprivFontStyleData *d)
{
CFStringRef styleName;
BOOL isOblique;
isOblique = NO; // default value
styleName = [d styleName];
if (styleName != NULL) {
CFRange range;
range = CFStringFind(styleName, CFSTR("Oblique"), kCFCompareBackwards);
if (range.location != kCFNotFound)
isOblique = YES;
}
if (isOblique)
return uiTextItalicOblique;
return uiTextItalicItalic;
}
// Italics are hard because Core Text does NOT distinguish between italic and oblique.
// All Core Text provides is a slant value and the italic bit of the symbolic traits mask.
// However, Core Text does seem to guarantee (from experimentation; see below) that the slant will be nonzero if and only if the italic bit is set, so we don't need to use the slant value.
// Core Text also seems to guarantee that if a font lists itself as Italic or Oblique by name (font subfamily name, font style name, whatever), it will also have that bit set, so testing this bit does cover all fonts that name themselves as Italic and Oblique. (Again, this is from the below experimentation.)
// TODO there is still one catch that might matter from a user's POV: the reverse is not true the italic bit can be set even if the style of the font face/subfamily/style isn't named as Italic (for example, script typefaces like Adobe's Palace Script MT Std); I don't know what to do about this... I know how to start: find a script font that has an italic form (Adobe's Palace Script MT Std does not; only Regular and Semibold)
static void setItalic(uiprivFontStyleData *d, uiFontDescriptor *out)
{
out->Italic = uiTextItalicNormal;
if (([d symbolicTraits] & kCTFontItalicTrait) != 0)
out->Italic = guessItalicOblique(d);
}
static void fillDescStyleFields(uiprivFontStyleData *d, NSDictionary *axisDict, uiFontDescriptor *out)
{
setItalic(d, out);
if (axisDict != nil)
uiprivProcessFontVariation(d, axisDict, out);
else
uiprivProcessFontTraits(d, out);
}
static CTFontDescriptorRef matchStyle(CTFontDescriptorRef against, uiFontDescriptor *styles)
{
CFArrayRef matching;
CFIndex i, n;
struct closeness *closeness;
CTFontDescriptorRef current;
CTFontDescriptorRef out;
uiprivFontStyleData *d;
NSDictionary *axisDict;
matching = CTFontDescriptorCreateMatchingFontDescriptors(against, NULL);
if (matching == NULL)
// no matches; give the original back and hope for the best
return against;
n = CFArrayGetCount(matching);
if (n == 0) {
// likewise
CFRelease(matching);
return against;
}
current = (CTFontDescriptorRef) CFArrayGetValueAtIndex(matching, 0);
d = [[uiprivFontStyleData alloc] initWithDescriptor:current];
axisDict = nil;
if ([d variation] != NULL)
axisDict = uiprivMakeVariationAxisDict([d variationAxes], [d table:kCTFontTableAvar]);
closeness = (struct closeness *) uiprivAlloc(n * sizeof (struct closeness), "struct closeness[]");
for (i = 0; i < n; i++) {
uiFontDescriptor fields;
closeness[i].index = i;
if (i != 0) {
current = (CTFontDescriptorRef) CFArrayGetValueAtIndex(matching, i);
d = [[uiprivFontStyleData alloc] initWithDescriptor:current];
}
fillDescStyleFields(d, axisDict, &fields);
closeness[i].weight = fields.Weight - styles->Weight;
closeness[i].italic = italicClosenesses[styles->Italic][fields.Italic];
closeness[i].stretch = fields.Stretch - styles->Stretch;
[d release];
}
// now figure out the 3-space difference between the three and sort by that
// TODO merge this loop with the previous loop?
for (i = 0; i < n; i++) {
double weight, italic, stretch;
weight = (double) (closeness[i].weight);
weight *= weight;
italic = closeness[i].italic;
italic *= italic;
stretch = (double) (closeness[i].stretch);
stretch *= stretch;
closeness[i].distance = sqrt(weight + italic + stretch);
}
qsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) {
const struct closeness *a = (const struct closeness *) aa;
const struct closeness *b = (const struct closeness *) bb;
// via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions
// LONGTERM is this really the best way? isn't it the same as if (*a < *b) return -1; if (*a > *b) return 1; return 0; ?
return (a->distance > b->distance) - (a->distance < b->distance);
});
// and the first element of the sorted array is what we want
out = CFArrayGetValueAtIndex(matching, closeness[0].index);
CFRetain(out); // get rule
// release everything
if (axisDict != nil)
[axisDict release];
uiprivFree(closeness);
CFRelease(matching);
// and release the original descriptor since we no longer need it
CFRelease(against);
return out;
}
CTFontDescriptorRef uiprivFontDescriptorToCTFontDescriptor(uiFontDescriptor *fd)
{
CFMutableDictionaryRef attrs;
CFStringRef cffamily;
CFNumberRef cfsize;
CTFontDescriptorRef basedesc;
attrs = CFDictionaryCreateMutable(NULL, 2,
// TODO are these correct?
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (attrs == NULL) {
// TODO
}
cffamily = CFStringCreateWithCString(NULL, fd->Family, kCFStringEncodingUTF8);
if (cffamily == NULL) {
// TODO
}
CFDictionaryAddValue(attrs, kCTFontFamilyNameAttribute, cffamily);
CFRelease(cffamily);
cfsize = CFNumberCreate(NULL, kCFNumberDoubleType, &(fd->Size));
CFDictionaryAddValue(attrs, kCTFontSizeAttribute, cfsize);
CFRelease(cfsize);
basedesc = CTFontDescriptorCreateWithAttributes(attrs);
CFRelease(attrs); // TODO correct?
return matchStyle(basedesc, fd);
}
// fortunately features that aren't supported are simply ignored, so we can copy them all in
CTFontDescriptorRef uiprivCTFontDescriptorAppendFeatures(CTFontDescriptorRef desc, const uiOpenTypeFeatures *otf)
{
CTFontDescriptorRef new;
CFArrayRef featuresArray;
CFDictionaryRef attrs;
const void *keys[1], *values[1];
featuresArray = uiprivOpenTypeFeaturesToCTFeatures(otf);
keys[0] = kCTFontFeatureSettingsAttribute;
values[0] = featuresArray;
attrs = CFDictionaryCreate(NULL,
keys, values, 1,
// TODO are these correct?
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFRelease(featuresArray);
new = CTFontDescriptorCreateCopyWithAttributes(desc, attrs);
CFRelease(attrs);
CFRelease(desc);
return new;
}
void uiprivFontDescriptorFromCTFontDescriptor(CTFontDescriptorRef ctdesc, uiFontDescriptor *uidesc)
{
CFStringRef cffamily;
uiprivFontStyleData *d;
NSDictionary *axisDict;
cffamily = (CFStringRef) CTFontDescriptorCopyAttribute(ctdesc, kCTFontFamilyNameAttribute);
if (cffamily == NULL) {
// TODO
}
// TODO normalize this by adding a uiDarwinCFStringToText()
uidesc->Family = uiDarwinNSStringToText((NSString *) cffamily);
CFRelease(cffamily);
d = [[uiprivFontStyleData alloc] initWithDescriptor:ctdesc];
axisDict = nil;
if ([d variation] != NULL)
axisDict = uiprivMakeVariationAxisDict([d variationAxes], [d table:kCTFontTableAvar]);
fillDescStyleFields(d, axisDict, uidesc);
if (axisDict != nil)
[axisDict release];
[d release];
}

223
darwin/fonttraits.m Normal file
View File

@ -0,0 +1,223 @@
// 1 november 2017
#import "uipriv_darwin.h"
#import "attrstr.h"
// This is the part of the font style matching and normalization code
// that handles fonts that use a traits dictionary.
//
// Matching stupidity: Core Text requires an **exact match for the
// entire traits dictionary**, otherwise it will **drop ALL the traits**.
//
// Normalization stupidity: Core Text uses its own scaled values for
// weight and width, but the values are different if the font is not
// registered and if said font is TrueType or OpenType. The values
// for all other cases do have some named constants starting with
// OS X 10.11, but even these aren't very consistent in practice.
//
// Of course, none of this is documented anywhere, so I had to do
// both trial-and-error AND reverse engineering to figure out what's
// what. We'll just convert Core Text's values into libui constants
// and use those for matching.
static BOOL fontRegistered(uiprivFontStyleData *d)
{
if (![d hasRegistrationScope])
// header says this should be treated as the same as not registered
return NO;
// examination of Core Text shows this is accurate
return [d registrationScope] != kCTFontManagerScopeNone;
}
// Core Text does (usWidthClass / 10) - 0.5 here.
// This roughly maps to our values with increments of 0.1, except for the fact 0 and 10 are allowed by Core Text, despite being banned by TrueType and OpenType themselves.
// We'll just treat them as identical to 1 and 9, respectively.
static const uiTextStretch os2WidthsToStretches[] = {
uiTextStretchUltraCondensed,
uiTextStretchUltraCondensed,
uiTextStretchExtraCondensed,
uiTextStretchCondensed,
uiTextStretchSemiCondensed,
uiTextStretchNormal,
uiTextStretchSemiExpanded,
uiTextStretchExpanded,
uiTextStretchExtraExpanded,
uiTextStretchUltraExpanded,
uiTextStretchUltraExpanded,
};
static const CFStringRef exceptions[] = {
CFSTR("LucidaGrande"),
CFSTR(".LucidaGrandeUI"),
CFSTR("STHeiti"),
CFSTR("STXihei"),
CFSTR("TimesNewRomanPSMT"),
NULL,
};
static void trySecondaryOS2Values(uiprivFontStyleData *d, uiFontDescriptor *out, BOOL *hasWeight, BOOL *hasWidth)
{
CFDataRef os2;
uint16_t usWeightClass, usWidthClass;
CFStringRef psname;
const CFStringRef *ex;
*hasWeight = NO;
*hasWidth = NO;
// only applies to unregistered fonts
if (fontRegistered(d))
return;
os2 = [d table:kCTFontTableOS2];
if (os2 == NULL)
// no OS2 table, so no secondary values
return;
if (CFDataGetLength(os2) > 77) {
const UInt8 *b;
b = CFDataGetBytePtr(os2);
usWeightClass = ((uint16_t) (b[4])) << 8;
usWeightClass |= (uint16_t) (b[5]);
if (usWeightClass <= 1000) {
if (usWeightClass < 11)
usWeightClass *= 100;
*hasWeight = YES;
}
usWidthClass = ((uint16_t) (b[6])) << 8;
usWidthClass |= (uint16_t) (b[7]);
if (usWidthClass <= 10)
*hasWidth = YES;
} else {
usWeightClass = 0;
*hasWeight = YES;
usWidthClass = 0;
*hasWidth = YES;
}
if (*hasWeight)
// we can just use this directly
out->Weight = usWeightClass;
if (*hasWidth)
out->Stretch = os2WidthsToStretches[usWidthClass];
CFRelease(os2);
// don't use secondary weights in the event of special predefined names
psname = [d postScriptName];
for (ex = exceptions; *ex != NULL; ex++)
if (CFEqual(psname, *ex)) {
*hasWeight = NO;
break;
}
}
static BOOL testTTFOTFSubfamilyName(CFStringRef name, CFStringRef want)
{
CFRange range;
if (name == NULL)
return NO;
range.location = 0;
range.length = CFStringGetLength(name);
return CFStringFindWithOptions(name, want, range,
(kCFCompareCaseInsensitive | kCFCompareBackwards | kCFCompareNonliteral), NULL) != false;
}
static BOOL testTTFOTFSubfamilyNames(uiprivFontStyleData *d, CFStringRef want)
{
switch ([d fontFormat]) {
case kCTFontFormatOpenTypePostScript:
case kCTFontFormatOpenTypeTrueType:
case kCTFontFormatTrueType:
break;
default:
return NO;
}
if (testTTFOTFSubfamilyName([d preferredSubFamilyName], want))
return YES;
if (testTTFOTFSubfamilyName([d subFamilyName], want))
return YES;
if (testTTFOTFSubfamilyName([d fullName], want))
return YES;
if (testTTFOTFSubfamilyName([d preferredFamilyName], want))
return YES;
return testTTFOTFSubfamilyName([d familyName], want);
}
// work around a bug in libFontRegistry.dylib
static BOOL shouldReallyBeThin(uiprivFontStyleData *d)
{
return testTTFOTFSubfamilyNames(d, CFSTR("W1"));
}
// work around a bug in libFontRegistry.dylib
static BOOL shouldReallyBeSemiCondensed(uiprivFontStyleData *d)
{
return testTTFOTFSubfamilyNames(d, CFSTR("Semi Condensed"));
}
void uiprivProcessFontTraits(uiprivFontStyleData *d, uiFontDescriptor *out)
{
double weight, width;
BOOL hasWeight, hasWidth;
hasWeight = NO;
hasWidth = NO;
trySecondaryOS2Values(d, out, &hasWeight, &hasWidth);
weight = [d weight];
width = [d width];
if (!hasWeight)
// TODO this scale is a bit lopsided
if (weight <= -0.7)
out->Weight = uiTextWeightThin;
else if (weight <= -0.5)
out->Weight = uiTextWeightUltraLight;
else if (weight <= -0.3)
out->Weight = uiTextWeightLight;
else if (weight <= -0.23) {
out->Weight = uiTextWeightBook;
if (shouldReallyBeThin(d))
out->Weight = uiTextWeightThin;
} else if (weight <= 0.0)
out->Weight = uiTextWeightNormal;
else if (weight <= 0.23)
out->Weight = uiTextWeightMedium;
else if (weight <= 0.3)
out->Weight = uiTextWeightSemiBold;
else if (weight <= 0.4)
out->Weight = uiTextWeightBold;
else if (weight <= 0.5)
out->Weight = uiTextWeightUltraBold;
else if (weight <= 0.7)
out->Weight = uiTextWeightHeavy;
else
out->Weight = uiTextWeightUltraHeavy;
if (!hasWidth)
// TODO this scale is a bit lopsided
if (width <= -0.7) {
out->Stretch = uiTextStretchUltraCondensed;
if (shouldReallyBeSemiCondensed(d))
out->Stretch = uiTextStretchSemiCondensed;
} else if (width <= -0.5)
out->Stretch = uiTextStretchExtraCondensed;
else if (width <= -0.2)
out->Stretch = uiTextStretchCondensed;
else if (width <= -0.1)
out->Stretch = uiTextStretchSemiCondensed;
else if (width <= 0.0)
out->Stretch = uiTextStretchNormal;
else if (width <= 0.1)
out->Stretch = uiTextStretchSemiExpanded;
else if (width <= 0.2)
out->Stretch = uiTextStretchExpanded;
else if (width <= 0.6)
out->Stretch = uiTextStretchExtraExpanded;
else
out->Stretch = uiTextStretchUltraExpanded;
}

336
darwin/fontvariation.m Normal file
View File

@ -0,0 +1,336 @@
// 2 november 2017
#import "uipriv_darwin.h"
#import "attrstr.h"
// This is the part of the font style matching and normalization code
// that handles fonts that use the fvar table.
//
// Matching stupidity: Core Text **doesn't even bother** matching
// these, even if you tell it to do so explicitly. It'll always return
// all variations for a given font.
//
// Normalization stupidity: Core Text doesn't normalize the fvar
// table values for us, so we'll have to do it ourselves. Furthermore,
// Core Text doesn't provide an API for accessing the avar table, if
// any, so we must do so ourselves. (TODO does Core Text even
// follow the avar table if a font has it?)
//
// Thankfully, normalization is well-defined in both TrueType and
// OpenType and seems identical in both, so we can just normalize
// the values and then convert them linearly to libui values for
// matching.
//
// References:
// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6fvar.html
// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6avar.html
// - https://www.microsoft.com/typography/otspec/fvar.htm
// - https://www.microsoft.com/typography/otspec/otvaroverview.htm#CSN
// - https://www.microsoft.com/typography/otspec/otff.htm
// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html#Types
// - https://www.microsoft.com/typography/otspec/avar.htm
// TODO Skia doesn't quite map correctly; notice what passes for condensed in the drawtext example
// TODO also investigate Marker Felt not working right in Thin and Wide modes (but that's probably the other file, putting it here just so I don't forget)
#define fvarWeight 0x77676874
#define fvarWidth 0x77647468
// TODO explain why these are signed
typedef int32_t fixed1616;
typedef int16_t fixed214;
// note that Microsoft's data type list implies that *all* fixed-point types have the same format; it only gives specific examples for the 2.14 format, which confused me because I thought 16.16 worked differently, but eh
static fixed1616 doubleToFixed1616(double d)
{
double ipart, fpart;
long flong;
int16_t i16;
uint32_t ret;
fpart = modf(d, &ipart);
// fpart must be unsigned; modf() gives us fpart with the same sign as d (so we have to adjust both ipart and fpart appropriately)
if (fpart < 0) {
ipart -= 1;
fpart = 1 + fpart;
}
fpart *= 65536;
flong = lround(fpart);
i16 = (int16_t) ipart;
ret = (uint32_t) ((uint16_t) i16);
ret <<= 16;
ret |= (uint16_t) (flong & 0xFFFF);
return (fixed1616) ret;
}
// see also https://stackoverflow.com/questions/8506317/fixed-point-unsigned-division-in-c and freetype's FT_DivFix()
// TODO figure out the specifics of freetype's more complex implementation that shifts b and juggles signs
static fixed1616 fixed1616Divide(fixed1616 a, fixed1616 b)
{
uint32_t u;
int64_t a64;
u = (uint32_t) a;
a64 = (int64_t) (((uint64_t) u) << 16);
return (fixed1616) (a64 / b);
}
static fixed214 fixed1616ToFixed214(fixed1616 f)
{
uint32_t t;
uint32_t topbit;
t = (uint32_t) (f + 0x00000002);
topbit = t & 0x80000000;
t >>= 2;
if (topbit != 0)
t |= 0xC000000;
return (fixed214) (t & 0xFFFF);
}
static double fixed214ToDouble(fixed214 f)
{
uint16_t u;
double base;
double frac;
u = (uint16_t) f;
switch ((u >> 14) & 0x3) {
case 0:
base = 0;
break;
case 1:
base = 1;
break;
case 2:
base = -2;
break;
case 3:
base = -1;
}
frac = ((double) (u & 0x3FFF)) / 16384;
return base + frac;
}
static fixed1616 fixed214ToFixed1616(fixed214 f)
{
int32_t t;
t = (int32_t) ((int16_t) f);
t <<= 2;
return (fixed1616) (t - 0x00000002);
}
static const fixed1616 fixed1616Negative1 = (int32_t) ((uint32_t) 0xFFFF0000);
static const fixed1616 fixed1616Zero = 0x00000000;
static const fixed1616 fixed1616Positive1 = 0x00010000;
static fixed1616 fixed1616Normalize(fixed1616 val, fixed1616 min, fixed1616 max, fixed1616 def)
{
if (val < min)
val = min;
if (val > max)
val = max;
if (val < def)
return fixed1616Divide(-(def - val), (def - min));
if (val > def)
return fixed1616Divide((val - def), (max - def));
return fixed1616Zero;
}
static fixed214 normalizedTo214(fixed1616 val, const fixed1616 *avarMappings, size_t avarCount)
{
if (val < fixed1616Negative1)
val = fixed1616Negative1;
if (val > fixed1616Positive1)
val = fixed1616Positive1;
if (avarCount != 0) {
size_t start, end;
fixed1616 startFrom, endFrom;
fixed1616 startTo, endTo;
for (end = 0; end < avarCount; end += 2) {
endFrom = avarMappings[end];
endTo = avarMappings[end + 1];
if (endFrom >= val)
break;
}
if (endFrom == val)
val = endTo;
else {
start = end - 2;
startFrom = avarMappings[start];
startTo = avarMappings[start + 1];
val = fixed1616Divide((val - startFrom), (endFrom - startFrom));
// TODO find a font with an avar table and make sure this works, or if we need to use special code for this too
val *= (endTo - startTo);
val += startTo;
}
}
return fixed1616ToFixed214(val);
}
static fixed1616 *avarExtract(CFDataRef table, CFIndex index, size_t *n)
{
const UInt8 *b;
size_t off;
size_t i, nEntries;
fixed1616 *entries;
fixed1616 *p;
b = CFDataGetBytePtr(table);
off = 8;
#define nextuint16be() ((((uint16_t) (b[off])) << 8) | ((uint16_t) (b[off + 1])))
for (; index > 0; index--) {
nEntries = (size_t) nextuint16be();
off += 2;
off += 4 * nEntries;
}
nEntries = nextuint16be();
*n = nEntries * 2;
entries = (fixed1616 *) uiprivAlloc(*n * sizeof (fixed1616), "fixed1616[]");
p = entries;
for (i = 0; i < *n; i++) {
*p++ = fixed214ToFixed1616((fixed214) nextuint16be());
off += 2;
}
return entries;
}
static BOOL extractAxisDictValue(CFDictionaryRef dict, CFStringRef key, fixed1616 *out)
{
CFNumberRef num;
double v;
num = (CFNumberRef) CFDictionaryGetValue(dict, key);
if (CFNumberGetValue(num, kCFNumberDoubleType, &v) == false)
return NO;
*out = doubleToFixed1616(v);
return YES;
}
// TODO here and elsewhere: make sure all Objective-C classes and possibly also custom method names have uipriv prefixes
@interface fvarAxis : NSObject {
fixed1616 min;
fixed1616 max;
fixed1616 def;
fixed1616 *avarMappings;
size_t avarCount;
}
- (id)initWithIndex:(CFIndex)i dict:(CFDictionaryRef)dict avarTable:(CFDataRef)table;
- (double)normalize:(double)v;
@end
@implementation fvarAxis
- (id)initWithIndex:(CFIndex)i dict:(CFDictionaryRef)dict avarTable:(CFDataRef)table
{
self = [super init];
if (self) {
self->avarMappings = NULL;
self->avarCount = 0;
if (!extractAxisDictValue(dict, kCTFontVariationAxisMinimumValueKey, &(self->min)))
goto fail;
if (!extractAxisDictValue(dict, kCTFontVariationAxisMaximumValueKey, &(self->max)))
goto fail;
if (!extractAxisDictValue(dict, kCTFontVariationAxisDefaultValueKey, &(self->def)))
goto fail;
if (table != NULL)
self->avarMappings = avarExtract(table, i, &(self->avarCount));
}
return self;
fail:
[self release];
return nil;
}
- (void)dealloc
{
if (self->avarMappings != NULL) {
uiprivFree(self->avarMappings);
self->avarMappings = NULL;
}
[super dealloc];
}
- (double)normalize:(double)d
{
fixed1616 n;
fixed214 n2;
n = doubleToFixed1616(d);
n = fixed1616Normalize(n, self->min, self->max, self->def);
n2 = normalizedTo214(n, self->avarMappings, self->avarCount);
return fixed214ToDouble(n2);
}
@end
NSDictionary *uiprivMakeVariationAxisDict(CFArrayRef axes, CFDataRef avarTable)
{
CFDictionaryRef axis;
CFIndex i, n;
NSMutableDictionary *out;
n = CFArrayGetCount(axes);
out = [NSMutableDictionary new];
for (i = 0; i < n; i++) {
CFNumberRef key;
axis = (CFDictionaryRef) CFArrayGetValueAtIndex(axes, i);
key = (CFNumberRef) CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey);
[out setObject:[[fvarAxis alloc] initWithIndex:i dict:axis avarTable:avarTable]
forKey:((NSNumber *) key)];
}
if (avarTable != NULL)
CFRelease(avarTable);
return out;
}
#define fvarAxisKey(n) [NSNumber numberWithUnsignedInteger:n]
static BOOL tryAxis(NSDictionary *axisDict, CFDictionaryRef var, NSNumber *key, double *out)
{
fvarAxis *axis;
CFNumberRef num;
axis = (fvarAxis *) [axisDict objectForKey:key];
if (axis == nil)
return NO;
num = (CFNumberRef) CFDictionaryGetValue(var, (CFNumberRef) key);
if (num == nil)
return NO;
if (CFNumberGetValue(num, kCFNumberDoubleType, out) == false) {
// TODO
return NO;
}
*out = [axis normalize:*out];
return YES;
}
void uiprivProcessFontVariation(uiprivFontStyleData *d, NSDictionary *axisDict, uiFontDescriptor *out)
{
CFDictionaryRef var;
double v;
out->Weight = uiTextWeightNormal;
out->Stretch = uiTextStretchNormal;
var = [d variation];
if (tryAxis(axisDict, var, fvarAxisKey(fvarWeight), &v)) {
// v is now a value between -1 and 1 scaled linearly between discrete points
// we want a linear value between 0 and 1000 with 400 being normal
if (v < 0) {
v += 1;
out->Weight = (uiTextWeight) (v * 400);
} else if (v > 0)
out->Weight += (uiTextWeight) (v * 600);
}
if (tryAxis(axisDict, var, fvarAxisKey(fvarWidth), &v)) {
// likewise, but with stretches, we go from 0 to 8 with 4 being directly between the two, so this is sufficient
v += 1;
out->Stretch = (uiTextStretch) (v * 4);
}
}

View File

@ -530,7 +530,7 @@ void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy)
// LONGTERM on other platforms
// or at leat allow this and implicitly turn it into a spacer
if (c == NULL)
userbug("You cannot add NULL to a uiForm.");
uiprivUserBug("You cannot add NULL to a uiForm.");
[f->view append:toNSString(label) c:c stretchy:stretchy];
}

53
darwin/future.m Normal file
View File

@ -0,0 +1,53 @@
// 19 may 2017
#import "uipriv_darwin.h"
// functions and constants FROM THE FUTURE!
// note: for constants, dlsym() returns the address of the constant itself, as if we had done &constantName
// added in OS X 10.10; we need 10.8
CFStringRef *FUTURE_kCTFontOpenTypeFeatureTag = NULL;
CFStringRef *FUTURE_kCTFontOpenTypeFeatureValue = NULL;
// added in OS X 10.12; we need 10.8
CFStringRef *FUTURE_kCTBackgroundColorAttributeName = NULL;
// note that we treat any error as "the symbols aren't there" (and don't care if dlclose() failed)
void loadFutures(void)
{
void *handle;
// dlsym() walks the dependency chain, so opening the current process should be sufficient
handle = dlopen(NULL, RTLD_LAZY);
if (handle == NULL)
return;
#define GET(var, fn) *((void **) (&var)) = dlsym(handle, #fn)
GET(FUTURE_kCTFontOpenTypeFeatureTag, kCTFontOpenTypeFeatureTag);
GET(FUTURE_kCTFontOpenTypeFeatureValue, kCTFontOpenTypeFeatureValue);
GET(FUTURE_kCTBackgroundColorAttributeName, kCTBackgroundColorAttributeName);
dlclose(handle);
}
// wrappers for methods that exist in the future that we can check for with respondsToSelector:
// keep them in one place for convenience
// apparently only added in 10.9; we need 10.8
void FUTURE_NSLayoutConstraint_setIdentifier(NSLayoutConstraint *constraint, NSString *identifier)
{
id cid = (id) constraint;
if ([constraint respondsToSelector:@selector(setIdentifier:)])
[cid setIdentifier:identifier];
}
// added in 10.11; we need 10.8
// return whether this was done because we recreate its effects if not (see winmoveresize.m)
BOOL FUTURE_NSWindow_performWindowDragWithEvent(NSWindow *w, NSEvent *initialEvent)
{
id cw = (id) w;
if ([w respondsToSelector:@selector(performWindowDragWithEvent:)]) {
[cw performWindowDragWithEvent:initialEvent];
return YES;
}
return NO;
}

59
darwin/graphemes.m Normal file
View File

@ -0,0 +1,59 @@
// 3 december 2016
#import "uipriv_darwin.h"
#import "attrstr.h"
// CFStringGetRangeOfComposedCharactersAtIndex() is the function for grapheme clusters
// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Strings/Articles/stringsClusters.html says that this does work on all multi-codepoint graphemes (despite the name), and that this is the preferred function for this particular job anyway
int uiprivGraphemesTakesUTF16(void)
{
return 1;
}
uiprivGraphemes *uiprivNewGraphemes(void *s, size_t len)
{
uiprivGraphemes *g;
UniChar *str = (UniChar *) s;
CFStringRef cfstr;
size_t ppos, gpos;
CFRange range;
size_t i;
g = uiprivNew(uiprivGraphemes);
cfstr = CFStringCreateWithCharactersNoCopy(NULL, str, len, kCFAllocatorNull);
if (cfstr == NULL) {
// TODO
}
// first figure out how many graphemes there are
g->len = 0;
ppos = 0;
while (ppos < len) {
range = CFStringGetRangeOfComposedCharactersAtIndex(cfstr, ppos);
g->len++;
ppos = range.location + range.length;
}
g->pointsToGraphemes = (size_t *) uiprivAlloc((len + 1) * sizeof (size_t), "size_t[] (graphemes)");
g->graphemesToPoints = (size_t *) uiprivAlloc((g->len + 1) * sizeof (size_t), "size_t[] (graphemes)");
// now calculate everything
// fortunately due to the use of CFRange we can do this in one loop trivially!
ppos = 0;
gpos = 0;
while (ppos < len) {
range = CFStringGetRangeOfComposedCharactersAtIndex(cfstr, ppos);
for (i = 0; i < range.length; i++)
g->pointsToGraphemes[range.location + i] = gpos;
g->graphemesToPoints[gpos] = range.location;
gpos++;
ppos = range.location + range.length;
}
// and set the last one
g->pointsToGraphemes[ppos] = gpos;
g->graphemesToPoints[gpos] = ppos;
CFRelease(cfstr);
return g;
}

View File

@ -288,11 +288,11 @@ struct uiGrid {
// now build a topological map of the grid gg[y][x]
// also figure out which cells contain spanned views so they can be ignored later
// treat hidden controls by keeping the indices -1
gg = (int **) uiAlloc(ycount * sizeof (int *), "int[][]");
gspan = (BOOL **) uiAlloc(ycount * sizeof (BOOL *), "BOOL[][]");
gg = (int **) uiprivAlloc(ycount * sizeof (int *), "int[][]");
gspan = (BOOL **) uiprivAlloc(ycount * sizeof (BOOL *), "BOOL[][]");
for (y = 0; y < ycount; y++) {
gg[y] = (int *) uiAlloc(xcount * sizeof (int), "int[]");
gspan[y] = (BOOL *) uiAlloc(xcount * sizeof (BOOL), "BOOL[]");
gg[y] = (int *) uiprivAlloc(xcount * sizeof (int), "int[]");
gspan[y] = (BOOL *) uiprivAlloc(xcount * sizeof (BOOL), "BOOL[]");
for (x = 0; x < xcount; x++)
gg[y][x] = -1; // empty
}
@ -344,9 +344,9 @@ struct uiGrid {
// now build a topological map of the grid's views gv[y][x]
// for any empty cell, create a dummy view
gv = (NSView ***) uiAlloc(ycount * sizeof (NSView **), "NSView *[][]");
gv = (NSView ***) uiprivAlloc(ycount * sizeof (NSView **), "NSView *[][]");
for (y = 0; y < ycount; y++) {
gv[y] = (NSView **) uiAlloc(xcount * sizeof (NSView *), "NSView *[]");
gv[y] = (NSView **) uiprivAlloc(xcount * sizeof (NSView *), "NSView *[]");
for (x = 0; x < xcount; x++)
if (gg[y][x] == -1) {
gv[y][x] = [[NSView alloc] initWithFrame:NSZeroRect];
@ -360,8 +360,8 @@ struct uiGrid {
}
// now figure out which rows and columns really expand
hexpand = (BOOL *) uiAlloc(xcount * sizeof (BOOL), "BOOL[]");
vexpand = (BOOL *) uiAlloc(ycount * sizeof (BOOL), "BOOL[]");
hexpand = (BOOL *) uiprivAlloc(xcount * sizeof (BOOL), "BOOL[]");
vexpand = (BOOL *) uiprivAlloc(ycount * sizeof (BOOL), "BOOL[]");
// first, which don't span
for (gc in self->children) {
if (!uiControlVisible(gc.c))
@ -522,16 +522,16 @@ struct uiGrid {
// TODO make all expanding rows/columns the same height/width
// and finally clean up
uiFree(hexpand);
uiFree(vexpand);
uiprivFree(hexpand);
uiprivFree(vexpand);
for (y = 0; y < ycount; y++) {
uiFree(gg[y]);
uiFree(gv[y]);
uiFree(gspan[y]);
uiprivFree(gg[y]);
uiprivFree(gv[y]);
uiprivFree(gspan[y]);
}
uiFree(gg);
uiFree(gv);
uiFree(gspan);
uiprivFree(gg);
uiprivFree(gv);
uiprivFree(gspan);
}
- (void)append:(gridChild *)gc
@ -574,7 +574,7 @@ struct uiGrid {
break;
}
if (!found)
userbug("Existing control %p is not in grid %p; you cannot add other controls next to it", c, self->g);
uiprivUserBug("Existing control %p is not in grid %p; you cannot add other controls next to it", c, self->g);
switch (at) {
case uiAtLeading:
@ -742,9 +742,9 @@ static gridChild *toChild(uiControl *c, int xspan, int yspan, int hexpand, uiAli
gridChild *gc;
if (xspan < 0)
userbug("You cannot have a negative xspan in a uiGrid cell.");
uiprivUserBug("You cannot have a negative xspan in a uiGrid cell.");
if (yspan < 0)
userbug("You cannot have a negative yspan in a uiGrid cell.");
uiprivUserBug("You cannot have a negative yspan in a uiGrid cell.");
gc = [gridChild new];
gc.xspan = xspan;
gc.yspan = yspan;
@ -763,7 +763,7 @@ void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int ysp
// LONGTERM on other platforms
// or at leat allow this and implicitly turn it into a spacer
if (c == NULL)
userbug("You cannot add NULL to a uiGrid.");
uiprivUserBug("You cannot add NULL to a uiGrid.");
gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign, g);
gc.left = left;
gc.top = top;

View File

@ -11,23 +11,23 @@ uiImage *uiNewImage(double width, double height)
{
uiImage *i;
i = uiNew(uiImage);
i = uiprivNew(uiImage);
i->size = NSMakeSize(width, height);
i->i = [[NSImage alloc] initWithSize:i->size];
i->swizzled = [NSMutableArray new];
return i;
}
void uiFreeImage(uiImage *i)
void Image(uiImage *i)
{
NSValue *v;
[i->i release];
// to be safe, do this after releasing the image
for (v in i->swizzled)
uiFree([v pointerValue]);
uiprivFree([v pointerValue]);
[i->swizzled release];
uiFree(i);
uiprivFree(i);
}
void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride)
@ -40,7 +40,7 @@ void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, in
// OS X demands that R and B are in the opposite order from what we expect
// we must swizzle :(
// LONGTERM test on a big-endian system
swizzled = (uint8_t *) uiAlloc((pixelStride * pixelHeight * 4) * sizeof (uint8_t), "uint8_t[]");
swizzled = (uint8_t *) uiprivAlloc((pixelStride * pixelHeight * 4) * sizeof (uint8_t), "uint8_t[]");
bp = (uint8_t *) pixels;
sp = swizzled;
for (y = 0; y < pixelHeight * pixelStride; y += pixelStride)

View File

@ -1,5 +1,6 @@
// 6 april 2015
#import "uipriv_darwin.h"
#import "attrstr.h"
static BOOL canQuit = NO;
static NSAutoreleasePool *globalPool;
@ -26,7 +27,7 @@ static BOOL stepsIsRunning;
{
if (colorButtonInhibitSendAction(sel, from, to))
return NO;
if (fontButtonInhibitSendAction(sel, from, to))
if (uiprivFontButtonInhibitSendAction(sel, from, to))
return NO;
return [super sendAction:sel to:to from:from];
}
@ -38,7 +39,7 @@ static BOOL stepsIsRunning;
{
id override;
if (fontButtonOverrideTargetForAction(sel, from, to, &override))
if (uiprivFontButtonOverrideTargetForAction(sel, from, to, &override))
return override;
return [super targetForAction:sel to:to from:from];
}
@ -56,7 +57,7 @@ static BOOL stepsIsRunning;
NSEvent *e;
if (!canQuit)
implbug("call to [NSApp terminate:] when not ready to terminate; definitely contact andlabs");
uiprivImplBug("call to [NSApp terminate:] when not ready to terminate; definitely contact andlabs");
[realNSApp() stop:realNSApp()];
// stop: won't register until another event has passed; let's synthesize one
@ -90,7 +91,7 @@ static BOOL stepsIsRunning;
{
// for debugging
NSLog(@"in applicationShouldTerminate:");
if (shouldQuit()) {
if (uiprivShouldQuit()) {
canQuit = YES;
// this will call terminate:, which is the same as uiQuit()
return NSTerminateNow;
@ -105,12 +106,12 @@ static BOOL stepsIsRunning;
@end
uiInitOptions options;
uiInitOptions uiprivOptions;
const char *uiInit(uiInitOptions *o)
{
@autoreleasepool {
options = *o;
uiprivOptions = *o;
app = [[applicationClass sharedApplication] retain];
// don't check for a NO return; something (launch services?) causes running from application bundles to always return NO when asking to change activation policy, even if the change is to the same activation policy!
// see https://github.com/andlabs/ui/issues/6
@ -119,12 +120,16 @@ const char *uiInit(uiInitOptions *o)
[realNSApp() setDelegate:delegate];
initAlloc();
loadFutures();
loadUndocumented();
// always do this so we always have an application menu
appDelegate().menuManager = [[menuManager new] autorelease];
[realNSApp() setMainMenu:[appDelegate().menuManager makeMenubar]];
setupFontPanel();
uiprivSetupFontPanel();
uiprivInitUnderlineColors();
}
globalPool = [[NSAutoreleasePool alloc] init];
@ -134,12 +139,12 @@ const char *uiInit(uiInitOptions *o)
void uiUninit(void)
{
if (!globalPool) {
userbug("You must call uiInit() first!");
}
if (!globalPool)
uiprivUserBug("You must call uiInit() first!");
[globalPool release];
@autoreleasepool {
uiprivUninitUnderlineColors();
[delegate release];
[realNSApp() setDelegate:nil];
[app release];
@ -237,3 +242,44 @@ void uiQueueMain(void (*f)(void *data), void *data)
// the signature of f matches dispatch_function_t
dispatch_async_f(dispatch_get_main_queue(), data, f);
}
@interface uiprivTimerDelegate : NSObject {
int (*f)(void *data);
void *data;
}
- (id)initWithCallback:(int (*)(void *))callback data:(void *)callbackData;
- (void)doTimer:(NSTimer *)timer;
@end
@implementation uiprivTimerDelegate
- (id)initWithCallback:(int (*)(void *))callback data:(void *)callbackData
{
self = [super init];
if (self) {
self->f = callback;
self->data = callbackData;
}
return self;
}
- (void)doTimer:(NSTimer *)timer
{
if (!(*(self->f))(self->data))
[timer invalidate];
}
@end
void uiTimer(int milliseconds, int (*f)(void *data), void *data)
{
uiprivTimerDelegate *delegate;
delegate = [[uiprivTimerDelegate alloc] initWithCallback:f data:data];
[NSTimer scheduledTimerWithTimeInterval:(milliseconds / 1000.0)
target:delegate
selector:@selector(doTimer:)
userInfo:nil
repeats:YES];
[delegate release];
}

View File

@ -12,7 +12,7 @@ struct mapTable *newMap(void)
{
struct mapTable *m;
m = uiNew(struct mapTable);
m = uiprivNew(struct mapTable);
m->m = [[NSMapTable alloc] initWithKeyOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality)
valueOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality)
capacity:0];
@ -22,9 +22,9 @@ struct mapTable *newMap(void)
void mapDestroy(struct mapTable *m)
{
if ([m->m count] != 0)
implbug("attempt to destroy map with items inside");
uiprivImplBug("attempt to destroy map with items inside");
[m->m release];
uiFree(m);
uiprivFree(m);
}
void *mapGet(struct mapTable *m, void *key)

View File

@ -71,7 +71,7 @@ static void mapItemReleaser(void *key, void *value)
- (IBAction)onQuitClicked:(id)sender
{
if (shouldQuit())
if (uiprivShouldQuit())
uiQuit();
}
@ -80,17 +80,17 @@ static void mapItemReleaser(void *key, void *value)
switch (smi->type) {
case typeQuit:
if (self->hasQuit)
userbug("You can't have multiple Quit menu items in one program.");
uiprivUserBug("You can't have multiple Quit menu items in one program.");
self->hasQuit = YES;
break;
case typePreferences:
if (self->hasPreferences)
userbug("You can't have multiple Preferences menu items in one program.");
uiprivUserBug("You can't have multiple Preferences menu items in one program.");
self->hasPreferences = YES;
break;
case typeAbout:
if (self->hasAbout)
userbug("You can't have multiple About menu items in one program.");
uiprivUserBug("You can't have multiple About menu items in one program.");
self->hasAbout = YES;
break;
}
@ -212,7 +212,7 @@ void uiMenuItemDisable(uiMenuItem *item)
void uiMenuItemOnClicked(uiMenuItem *item, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)
{
if (item->type == typeQuit)
userbug("You can't call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead.");
uiprivUserBug("You can't call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead.");
item->onClicked = f;
item->onClickedData = data;
}
@ -239,9 +239,9 @@ static uiMenuItem *newItem(uiMenu *m, int type, const char *name)
uiMenuItem *item;
if (menusFinalized)
userbug("You can't create a new menu item after menus have been finalized.");
uiprivUserBug("You can't create a new menu item after menus have been finalized.");
item = uiNew(uiMenuItem);
item = uiprivNew(uiMenuItem);
item->type = type;
switch (item->type) {
@ -315,11 +315,11 @@ uiMenu *uiNewMenu(const char *name)
uiMenu *m;
if (menusFinalized)
userbug("You can't create a new menu after menus have been finalized.");
uiprivUserBug("You can't create a new menu after menus have been finalized.");
if (menus == nil)
menus = [NSMutableArray new];
m = uiNew(uiMenu);
m = uiprivNew(uiMenu);
m->menu = [[NSMenu alloc] initWithTitle:toNSString(name)];
// use automatic menu item enabling for all menus for consistency's sake
@ -359,10 +359,10 @@ void uninitMenus(void)
v = (NSValue *) obj;
mi = (uiMenuItem *) [v pointerValue];
uiFree(mi);
uiprivFree(mi);
}];
[m->items release];
uiFree(m);
uiprivFree(m);
}];
[menus release];
}

113
darwin/opentype.m Normal file
View File

@ -0,0 +1,113 @@
// 11 may 2017
#import "uipriv_darwin.h"
#import "attrstr.h"
struct addCTFeatureEntryParams {
CFMutableArrayRef array;
const void *tagKey;
BOOL tagIsNumber;
CFNumberType tagType;
const void *tagValue;
const void *valueKey;
CFNumberType valueType;
const void *valueValue;
};
static void addCTFeatureEntry(struct addCTFeatureEntryParams *p)
{
CFDictionaryRef featureDict;
CFNumberRef tagNum, valueNum;
const void *keys[2], *values[2];
keys[0] = p->tagKey;
tagNum = NULL;
values[0] = p->tagValue;
if (p->tagIsNumber) {
tagNum = CFNumberCreate(NULL, p->tagType, p->tagValue);
values[0] = tagNum;
}
keys[1] = p->valueKey;
valueNum = CFNumberCreate(NULL, p->valueType, p->valueValue);
values[1] = valueNum;
featureDict = CFDictionaryCreate(NULL,
keys, values, 2,
// TODO are these correct?
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (featureDict == NULL) {
// TODO
}
CFArrayAppendValue(p->array, featureDict);
CFRelease(featureDict);
CFRelease(valueNum);
if (p->tagIsNumber)
CFRelease(tagNum);
}
static uiForEach otfArrayForEachAAT(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data)
{
__block struct addCTFeatureEntryParams p;
p.array = (CFMutableArrayRef) data;
p.tagIsNumber = YES;
uiprivOpenTypeToAAT(a, b, c, d, value, ^(uint16_t type, uint16_t selector) {
p.tagKey = kCTFontFeatureTypeIdentifierKey;
p.tagType = kCFNumberSInt16Type;
p.tagValue = (const SInt16 *) (&type);
p.valueKey = kCTFontFeatureSelectorIdentifierKey;
p.valueType = kCFNumberSInt16Type;
p.valueValue = (const SInt16 *) (&selector);
addCTFeatureEntry(&p);
});
return uiForEachContinue;
}
// TODO find out which fonts differ in AAT small caps and test them with this
static uiForEach otfArrayForEachOT(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data)
{
struct addCTFeatureEntryParams p;
char tagcstr[5];
CFStringRef tagstr;
p.array = (CFMutableArrayRef) data;
p.tagKey = *FUTURE_kCTFontOpenTypeFeatureTag;
p.tagIsNumber = NO;
tagcstr[0] = a;
tagcstr[1] = b;
tagcstr[2] = c;
tagcstr[3] = d;
tagcstr[4] = '\0';
tagstr = CFStringCreateWithCString(NULL, tagcstr, kCFStringEncodingUTF8);
if (tagstr == NULL) {
// TODO
}
p.tagValue = tagstr;
p.valueKey = *FUTURE_kCTFontOpenTypeFeatureValue;
p.valueType = kCFNumberSInt32Type;
p.valueValue = (const SInt32 *) (&value);
addCTFeatureEntry(&p);
CFRelease(tagstr);
return uiForEachContinue;
}
CFArrayRef uiprivOpenTypeFeaturesToCTFeatures(const uiOpenTypeFeatures *otf)
{
CFMutableArrayRef array;
uiOpenTypeFeaturesForEachFunc f;
array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
if (array == NULL) {
// TODO
}
f = otfArrayForEachAAT;
if (FUTURE_kCTFontOpenTypeFeatureTag != NULL && FUTURE_kCTFontOpenTypeFeatureValue != NULL)
f = otfArrayForEachOT;
uiOpenTypeFeaturesForEach(otf, f, array);
return array;
}

View File

@ -48,7 +48,7 @@ void uiProgressBarSetValue(uiProgressBar *p, int value)
}
if (value < 0 || value > 100)
userbug("Value %d out of range for a uiProgressBar.", value);
uiprivUserBug("Value %d out of range for a uiProgressBar.", value);
// on 10.8 there's an animation when the progress bar increases, just like with Aero
if (value == 100) {

Some files were not shown because too many files have changed in this diff Show More