typemytype / drawbot

http://www.drawbot.com
Other
401 stars 62 forks source link

formattedString.lineHeight is not consistent. #52

Open frankrolf opened 8 years ago

frankrolf commented 8 years ago

I noticed that setting the line height for a formatted string will do something, but not necessarily what I expect. Especially if there is more than one font, the line heights are all different:

doc_height, doc_width = sizes()['Letter']
newPage(doc_width, doc_height)

fill(0)
stroke(None)
margin = 30
textBoxSize = (margin,margin,doc_width-2 * margin,doc_height-2 * margin)
text = 'abcdefghijklmnopqrstuvwxyz\n'

fontSize = 40
lineHeight = fontSize * 1.2
fs = FormattedString()
fs.fontSize(fontSize)
fs.lineHeight(lineHeight)

lineNumber = 0

for font in installedFonts():
    fs.append(text, font=font)
    rect(margin, doc_height - margin - (lineHeight * lineNumber), doc_width - 2 * margin, 0.2)
    lineNumber += 1

maxLineNumber = lineNumber
overflow = textBox(fs, textBoxSize)

while len(unicode(overflow)) > 0:
    newPage(doc_width, doc_height)
    overflow = textBox(overflow, textBoxSize)
    for lineNumber in range(0, maxLineNumber):
        rect(margin, doc_height - margin - (lineHeight * lineNumber), doc_width - 2 * margin, 0.2)
justvanrossum commented 6 years ago

Ugh, this is indeed pretty nasty.

from random import choice

fs = FormattedString()
fs.fontSize(100)
fs.lineHeight(100)

for i in range(10):
    fontName = choice(installedFonts())
    fs.append("Hello\n", font=fontName)

box = (100, 100, 800, 800)

fill(1, 0, 0)
prev = None
for x, y in textBoxBaselines(fs, box):
    rect(0, y, width(), 1)
    if prev is not None:
        print(prev - y)
    prev = y

fill(0)
textBox(fs, box)
typemytype commented 6 years ago

what can I say... those are the values that coreText is calculating.

justvanrossum commented 6 years ago

Well, if CoreText doesn't make sense, we'll have to make sense for it I'm afraid... This is simply no way to do typography.

typemytype commented 6 years ago

textEdit.app has this exactly checkbox I dont see this reflecting in the api, like in a NSParagraphStyle

screen shot 2017-12-16 at 12 15 08
justvanrossum commented 6 years ago

We're doing a lot of calculation anyway, so if we can implement our own exactLineHeights=True thing somewhere (textBox()? FormattedString attr?), that would be great.

typemytype commented 6 years ago

I know but this becomes super huge super fast: textBox text overflow depends on line heights or what with multiple line heights in a single formatted string...

(Im hoping on some weird coreText settings to get the exact line height)

typemytype commented 6 years ago

the saga continues:

this explains clearly what happens while typesetting: https://medium.com/@at_underscore/nsparagraphstyle-explained-visually-a8659d1fbd6f

but it has no effect when using multiple fonts in the same formattedString

from random import choice

fs = FormattedString()
fs.fontSize(50)
fs.lineHeight(100)

fontNames = [
"Seravek-BoldItalic",
"Times-Italic",
"AvenirNextCondensed-Italic",
"STSong",
"STIXSizeThreeSym-Regular",
"BodoniSvtyTwoOSITCTT-BookIt",
"TamilMN-Bold",
"ArialUnicodeMS",
"AvenirNextCondensed-HeavyItalic",
"MesquiteStd",
]

import AppKit

for i in range(10):
    fontName = fontNames[i]

    para = AppKit.NSMutableParagraphStyle.alloc().init()
    para.setLineSpacing_(0)
    para.setLineHeightMultiple_(0)
    para.setMinimumLineHeight_(fs._lineHeight)
    para.setMaximumLineHeight_(fs._lineHeight)

    nsf = AppKit.NSFont.fontWithName_size_(fontName, fs._fontSize)
    attributes = {
            AppKit.NSFontAttributeName: nsf,
            AppKit.NSForegroundColorAttributeName: AppKit.NSColor.blackColor(),
            AppKit.NSParagraphStyleAttributeName: para
        }
    print max([para.minimumLineHeight(), min([nsf.defaultLineHeightForFont() * para.lineHeightMultiple(), para.maximumLineHeight()])]) + para.lineSpacing()

    txt = AppKit.NSAttributedString.alloc().initWithString_attributes_("Hello\n", attributes)
    fs._attributedString.appendAttributedString_(txt)

box = (100, 100, 800, 800)

fill(1, 0, 0)
prev = None
print
for x, y in textBoxBaselines(fs, box):
    rect(0, y, width(), 1)
    if prev is not None:
        print(prev - y)
    prev = y

fill(0)
textBox(fs, box)

fill(None)
stroke(0)
rect(*box)
typemytype commented 6 years ago

next step in the line height sequel:

trying to use CTParagraphStyleCreate as there could be a difference between a NSParagraphStyle and a CTParagraphStyle

from random import choice

fs = FormattedString()
fs.fontSize(50)
fs.lineHeight(82)

fontNames = [
"Seravek-BoldItalic",
"Times-Italic",
"AvenirNextCondensed-Italic",
"STSong",
"STIXSizeThreeSym-Regular",
"BodoniSvtyTwoOSITCTT-BookIt",
"TamilMN-Bold",
"ArialUnicodeMS",
"AvenirNextCondensed-HeavyItalic",
"MesquiteStd",
]

import AppKit
import CoreText
import objc
import struct

for i in range(10):
    fontName = fontNames[i]

    linespacing = 0
    minLineHeight = fs._lineHeight
    maxLineheight = fs._lineHeight
    lineSpaceAdjustment = -10000

    para = AppKit.NSMutableParagraphStyle.alloc().init()
    para.setLineSpacing_(linespacing)
    para.setLineHeightMultiple_(lineSpaceAdjustment)
    para.setMinimumLineHeight_(minLineHeight)
    para.setMaximumLineHeight_(maxLineheight)

    float_pack = "d"

    settings = [    
        CoreText.CTParagraphStyleSetting(
            spec=CoreText.kCTParagraphStyleSpecifierLineSpacing, 
            valueSize=CoreText.sizeof_CGFloat, 
            value=struct.pack(float_pack, linespacing)
        ),
        CoreText.CTParagraphStyleSetting(
            spec=CoreText.kCTParagraphStyleSpecifierMinimumLineHeight, 
            valueSize=CoreText.sizeof_CGFloat, 
            value=struct.pack(float_pack, minLineHeight)
        ),
        CoreText.CTParagraphStyleSetting(
            spec=CoreText.kCTParagraphStyleSpecifierMaximumLineHeight, 
            valueSize=CoreText.sizeof_CGFloat, 
            value=struct.pack(float_pack, maxLineheight)
        ),
        CoreText.CTParagraphStyleSetting(
            spec=CoreText.kCTParagraphStyleSpecifierLineSpacingAdjustment, 
            valueSize=CoreText.sizeof_CGFloat, 
            value=struct.pack(float_pack, lineSpaceAdjustment)
        )
    ]

    ctpara = CoreText.CTParagraphStyleCreate(settings, len(settings))

    nsf = AppKit.NSFont.fontWithName_size_(fontName, fs._fontSize)
    attributes = {
            AppKit.NSFontAttributeName: nsf,
            AppKit.NSForegroundColorAttributeName: AppKit.NSColor.blackColor(),
            # use the core text paragraph style in the attributed string           
            CoreText.kCTParagraphStyleAttributeName: ctpara
        }

    print nsf.defaultLineHeightForFont(), fontName    
    print max([para.minimumLineHeight(), min([nsf.defaultLineHeightForFont() * para.lineHeightMultiple(), para.maximumLineHeight()])]) + para.lineSpacing()

    txt = AppKit.NSAttributedString.alloc().initWithString_attributes_("Hello\n", attributes)
    fs._attributedString.appendAttributedString_(txt)

print

box = (100, 100, 800, 800)

fill(1, 0, 0)
prev = None
for x, y in textBoxBaselines(fs, box):
    rect(0, y, width(), 1)
    if prev is not None:
        print(prev - y)
    prev = y

fill(0)
textBox(fs, box)

fill(None)
stroke(0)
rect(*box)
LettError commented 6 years ago

😢