Skip to content

Commit

Permalink
Improve documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
tdewolff committed Apr 21, 2021
1 parent 81999a6 commit 8b06955
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 29 deletions.
1 change: 1 addition & 0 deletions font/woff.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ func ParseWOFF(b []byte) ([]byte, error) {
}

checksum := 0xB1B0AFBA - calcChecksum(w.Bytes())
_ = checksumAdjustment
// TODO: (WOFF) master checksum seems right, but we don't throw an error if it is off
//if checksumAdjustment != checksum {
// return nil, fmt.Errorf("bad checksum")
Expand Down
4 changes: 2 additions & 2 deletions resources/title/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ func draw(c *canvas.Context) {
c.DrawPath(x, 4, p)
x += adv

c.SetFillColor(color.RGBA{224, 224, 224, 255})
c.DrawPath(2, 2, canvas.Rect{0, 0, x - 2.0, 1.0}.ToPath())
//c.SetFillColor(color.RGBA{224, 224, 224, 255})
//c.DrawPath(2, 2, canvas.Rect{0, 0, x - 2.0, 1.0}.ToPath())
}
Binary file modified resources/title/title.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion text/bidi.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

package text

var FriBidi = false
var usesFriBidi = false

// Bidi maps the string from its logical order to the visual order to correctly display mixed LTR/RTL text. It returns a mapping of rune positions.
func Bidi(text string) (string, []int) {
// linear map
mapV2L := make([]int, len([]rune(text)))
Expand Down
3 changes: 2 additions & 1 deletion text/fribidi.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import (
"unsafe"
)

var FriBidi = true
var usesFriBidi = true

// Bidi maps the string from its logical order to the visual order to correctly display mixed LTR/RTL text. It returns a mapping of rune positions.
func Bidi(text string) (string, []int) {
str := []rune(text)
pbaseDir := C.FriBidiParType(C.FRIBIDI_PAR_ON) // neutral direction
Expand Down
13 changes: 11 additions & 2 deletions text/harfbuzz.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ import (

// Design inspired by https://github.com/npillmayer/tyse/blob/main/engine/text/textshaping/

// Shaper is a text shaper formatting a string in properly positioned glyphs.
type Shaper struct {
cb *C.char
blob *C.struct_hb_blob_t
face *C.struct_hb_face_t
fonts map[uint16]*C.struct_hb_font_t
}

// NewShaper returns a new text shaper.
func NewShaper(b []byte, index int) (Shaper, error) {
cb := (*C.char)(C.CBytes(b))
blob := C.hb_blob_create(cb, C.uint(len(b)), C.HB_MEMORY_MODE_WRITABLE, nil, nil)
Expand All @@ -46,10 +48,12 @@ func NewShaper(b []byte, index int) (Shaper, error) {
}, nil
}

// NewShaperSFNT returns a new text shaper using a SFNT structure.
func NewShaperSFNT(sfnt *font.SFNT) (Shaper, error) {
return NewShaper(sfnt.Data, 0)
}

// Destroy destroys the allocated C memory.
func (s Shaper) Destroy() {
for _, font := range s.fonts {
C.hb_font_destroy(font)
Expand All @@ -59,6 +63,7 @@ func (s Shaper) Destroy() {
C.free(unsafe.Pointer(s.cb))
}

// Shape shapes the string for a given direction, script, and language.
func (s Shaper) Shape(text string, ppem uint16, direction Direction, script Script, language string, features string, variations string) []Glyph {
font, ok := s.fonts[ppem]
if !ok {
Expand Down Expand Up @@ -95,7 +100,7 @@ func (s Shaper) Shape(text string, ppem uint16, direction Direction, script Scri
}
C.hb_buffer_guess_segment_properties(buf)

if FriBidi && Direction(C.hb_buffer_get_direction(buf)) == RightToLeft {
if usesFriBidi && Direction(C.hb_buffer_get_direction(buf)) == RightToLeft {
// FriBidi already reversed the direction
C.hb_buffer_set_direction(buf, C.hb_direction_t(LeftToRight))
}
Expand Down Expand Up @@ -153,6 +158,7 @@ func (s Shaper) Shape(text string, ppem uint16, direction Direction, script Scri
return glyphs
}

// ScriptItemizer divides the string in parts for each different script.
func ScriptItemizer(text string) []string {
i := 0
items := []string{}
Expand All @@ -174,8 +180,10 @@ func ScriptItemizer(text string) []string {
return items
}

// Direction is the text direction.
type Direction int

// see Direction
const (
DirectionInvalid Direction = C.HB_DIRECTION_INVALID
LeftToRight = C.HB_DIRECTION_LTR
Expand All @@ -184,9 +192,10 @@ const (
BottomToTop = C.HB_DIRECTION_BTT
)

// Script is the script.
type Script uint32

// Taken from github.com/npillmayer/gotype
// see Script
const (
ScriptCommon Script = C.HB_SCRIPT_COMMON
ScriptInherited Script = C.HB_SCRIPT_INHERITED
Expand Down
40 changes: 28 additions & 12 deletions text/linebreak.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,45 @@ import (
// https://github.com/robertknight/tex-linebreak (JavaScript)
// https://github.com/akuchling/texlib (Python)

// FairyTales is an example text.
const FairyTales = "In olden times when wish\u200Bing still helped one there\u2001lived a king\u2001whose daugh\u200Bters were all beau\u200Bti\u200Bful; and the young\u200Best was so beautiful that the sun it\u200Bself, which has seen so much, was aston\u200Bished when\u200Bever it shone in her face. Close by the king's castle lay a great dark for\u200Best, and un\u200Bder an old lime-tree in the for\u200Best was a well, and when the day was very warm, the king's child went out into the for\u200Best and sat down by the side of the cool foun\u200Btain; and when she was bored she took a golden ball, and threw it up on high and caught it; and this ball was her favor\u200Bite play\u200Bthing."

// FrenchSpacing enforces equal widths for inter-word and inter-sentence spaces
var FrenchSpacing = false

// stretchability and shrinkability of spaces
// Stretchability and shrinkability of spaces
var SpaceStretch = 1.0 / 2.0
var SpaceShrink = 1.0 / 3.0

// stretchability and shrinkability factors for inter-sentence and other types of spaces
// not used if FrenchSpacing is set
// Stretchability and shrinkability factors for inter-sentence and other types of spaces, not used if FrenchSpacing is set
var SentenceFactor = 3.0
var ColonFactor = 2.0
var SemicolonFactor = 1.5
var CommaFactor = 1.25

// algorithmic parameters
// Algorithmic parameters
var Tolerance = 10.0 // TeX uses 200
var DemeritsLine = 10.0
var DemeritsFlagged = 100.0 // TeX uses 10000
var DemeritsFitness = 100.0 // TeX uses 10000
var HyphenPenalty = 50.0
var Infinity = 1000.0 // in case of ratio, demerits becomes about 1e22

// Align is te text alignment.
type Align int

// see Align
const (
Left Align = iota
Right
Centered
Justified
)

// Type is the item type.
type Type int

// see Type
const (
BoxType Type = iota
GlueType
Expand All @@ -68,6 +72,7 @@ func (t Type) String() string {
return fmt.Sprintf("Type(%d)", t)
}

// Item is a box, glue or penalty item.
type Item struct {
Type
Width, Stretch, Shrink float64
Expand All @@ -76,13 +81,15 @@ type Item struct {
Size int // number of boxes (glyphs) compressed into one
}

// Box returns a box item.
func Box(width float64) Item {
return Item{
Type: BoxType,
Width: width,
}
}

// Glue returns a glue item.
func Glue(width, stretch, shrink float64) Item {
return Item{
Type: GlueType,
Expand All @@ -92,6 +99,7 @@ func Glue(width, stretch, shrink float64) Item {
}
}

// Penalty returns a panalty item.
func Penalty(width, penalty float64, flagged bool) Item {
return Item{
Type: PenaltyType,
Expand All @@ -101,6 +109,7 @@ func Penalty(width, penalty float64, flagged bool) Item {
}
}

// Breakpoint is a (possible) break point in the string.
type Breakpoint struct {
next, prev *Breakpoint
parent *Breakpoint
Expand All @@ -124,6 +133,7 @@ func (br *Breakpoint) String() string {
return s[:len(s)-1]
}

// Breakpoints is a list of break points.
type Breakpoints struct {
head, tail *Breakpoint
}
Expand All @@ -142,10 +152,12 @@ func (list *Breakpoints) String() string {
return s[:len(s)-1]
}

// Has return true if it contains break point b.
func (list *Breakpoints) Has(b *Breakpoint) bool {
return !(b.prev == nil && b.next == nil && (b != list.head || list.head == nil))
}

// Push adds break point b to the end of the list.
func (list *Breakpoints) Push(b *Breakpoint) {
if list.head == nil {
list.head = b
Expand All @@ -157,6 +169,7 @@ func (list *Breakpoints) Push(b *Breakpoint) {
}
}

// InsertBefore inserts break point b before at.
func (list *Breakpoints) InsertBefore(b *Breakpoint, at *Breakpoint) {
if list.Has(b) || !list.Has(at) {
return
Expand All @@ -171,6 +184,7 @@ func (list *Breakpoints) InsertBefore(b *Breakpoint, at *Breakpoint) {
at.prev = b
}

// Remove removes break point b.
func (list *Breakpoints) Remove(b *Breakpoint) {
if !list.Has(b) {
return
Expand All @@ -189,24 +203,24 @@ func (list *Breakpoints) Remove(b *Breakpoint) {
b.next = nil
}

type Linebreaker struct {
type linebreaker struct {
items []Item
activeNodes *Breakpoints
W, Y, Z float64
width float64
}

func NewLinebreaker(items []Item, width float64) *Linebreaker {
func newLinebreaker(items []Item, width float64) *linebreaker {
activeNodes := &Breakpoints{}
activeNodes.Push(&Breakpoint{Fitness: 1})
return &Linebreaker{
return &linebreaker{
items: items,
activeNodes: activeNodes,
width: width,
}
}

func (lb *Linebreaker) computeAdjustmentRatio(b int, active *Breakpoint) float64 {
func (lb *linebreaker) computeAdjustmentRatio(b int, active *Breakpoint) float64 {
// compute the adjustment ratio r from a to b
L := lb.W - active.W
if lb.items[b].Type == PenaltyType {
Expand All @@ -224,7 +238,7 @@ func (lb *Linebreaker) computeAdjustmentRatio(b int, active *Breakpoint) float64
return math.Min(math.Max(ratio, -Infinity), Infinity)
}

func (lb *Linebreaker) computeSum(b int) (float64, float64, float64) {
func (lb *linebreaker) computeSum(b int) (float64, float64, float64) {
// compute tw=(sum w)after(b), ty=(sum y)after(b), and tz=(sum z)after(b)
W, Y, Z := lb.W, lb.Y, lb.Z
for i, item := range lb.items[b:] {
Expand All @@ -239,7 +253,7 @@ func (lb *Linebreaker) computeSum(b int) (float64, float64, float64) {
return W, Y, Z
}

func (lb *Linebreaker) mainLoop(b int, tolerance float64) {
func (lb *linebreaker) mainLoop(b int, tolerance float64) {
item := lb.items[b]
active := lb.activeNodes.head

Expand Down Expand Up @@ -341,12 +355,13 @@ func (lb *Linebreaker) mainLoop(b int, tolerance float64) {
}
}

// Linebreak breaks a list of items using Donald Knuth's line breaking algorithm. See Donald E. Knuth and Michael F. Plass, "Breaking Paragraphs into Lines", 1981
func Linebreak(items []Item, width float64, looseness int) []*Breakpoint {
tolerance := Tolerance

START:
// create an active node representing the beginning of the paragraph
lb := NewLinebreaker(items, width)
lb := newLinebreaker(items, width)
// if index is a legal breakpoint then main loop
for b, item := range lb.items {
if item.Type == BoxType {
Expand Down Expand Up @@ -453,7 +468,7 @@ func isNewline(s string) bool {
return false
}

// GlyphsToItems converts a slice of glyphs into the box/glue/penalty items model as used by Knuth's line breaking algorithm. The SFNT and Size of each glyph must be set. Indent and align specify the indentation width of the first line and the alignment (left, right, centered, justified) of the lines respectively. Finally, stretchWidth is typically twice the width of a space and is inserted as stretchable glue for alignment purposes.
// GlyphsToItems converts a slice of glyphs into the box/glue/penalty items model as used by Knuth's line breaking algorithm. The SFNT and Size of each glyph must be set. Indent and align specify the indentation width of the first line and the alignment (left, right, centered, justified) of the lines respectively. Vertical should be true for vertical scripts.
func GlyphsToItems(glyphs []Glyph, indent float64, align Align, vertical bool) []Item {
if len(glyphs) == 0 {
return []Item{}
Expand Down Expand Up @@ -595,6 +610,7 @@ func GlyphsToItems(glyphs []Glyph, indent float64, align Align, vertical bool) [
return items
}

// LinebreakGlyphs breaks a slice of glyphs uing the given SFNT font and font size. The indent and width specify the first line's indentation and the maximum line's width respectively. Align sets the horizontal alignment of the text. The looseness specifies whether it is desirable to have less or more lines than optimal.
func LinebreakGlyphs(sfnt *font.SFNT, size float64, glyphs []Glyph, indent, width float64, align Align, looseness int) [][]Glyph {
if len(glyphs) == 0 {
return [][]Glyph{}
Expand Down
10 changes: 10 additions & 0 deletions text/shaper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import (
"github.com/tdewolff/canvas/font"
)

// Shaper is a text shaper formatting a string in properly positioned glyphs.
type Shaper struct {
sfnt *font.SFNT
}

// NewShaper returns a new text shaper.
func NewShaper(b []byte, index int) (Shaper, error) {
sfnt, err := font.ParseSFNT(b, index)
if err != nil {
Expand All @@ -20,15 +22,18 @@ func NewShaper(b []byte, index int) (Shaper, error) {
}, nil
}

// NewShaperSFNT returns a new text shaper using a SFNT structure.
func NewShaperSFNT(sfnt *font.SFNT) (Shaper, error) {
return Shaper{
sfnt: sfnt,
}, nil
}

// Destroy destroys the allocated C memory.
func (s Shaper) Destroy() {
}

// Shape shapes the string for a given direction, script, and language.
func (s Shaper) Shape(text string, ppem uint16, direction Direction, script Script, language string, features string, variations string) []Glyph {
glyphs := make([]Glyph, len([]rune(text)))
i := 0
Expand All @@ -48,12 +53,15 @@ func (s Shaper) Shape(text string, ppem uint16, direction Direction, script Scri
return glyphs
}

// ScriptItemizer divides the string in parts for each different script.
func ScriptItemizer(text string) []string {
return []string{text}
}

// Direction is the text direction.
type Direction int

// see Direction
const (
DirectionInvalid Direction = iota
LeftToRight
Expand All @@ -62,8 +70,10 @@ const (
BottomToTop
)

// Script is the script.
type Script uint32

// see Script
const (
ScriptInvalid Script = 0
)

0 comments on commit 8b06955

Please sign in to comment.