package harfbuzz
import (
"fmt"
"github.com/go-text/typesetting/font"
ot "github.com/go-text/typesetting/font/opentype"
"github.com/go-text/typesetting/font/opentype/tables"
ucd "github.com/go-text/typesetting/internal/unicodedata"
)
const (
NoScriptIndex = 0xFFFF
NoFeatureIndex = 0xFFFF
DefaultLanguageIndex = 0xFFFF
noVariationsIndex = -1
)
type otShapePlanner struct {
shaper otComplexShaper
props SegmentProperties
tables *font .Font
otMap otMapBuilder
aatMap aatMapBuilder
applyMorx bool
scriptZeroMarks bool
scriptFallbackPosition bool
}
func newOtShapePlanner(font *font .Font , props SegmentProperties ) *otShapePlanner {
var out otShapePlanner
out .props = props
out .tables = font
out .otMap = newOtMapBuilder (font , props )
out .aatMap = newAatMapBuilder (font , props )
out .applyMorx = len (font .Morx ) != 0 && (props .Direction .isHorizontal () || len (font .GSUB .Lookups ) == 0 )
out .shaper = categorizeComplex (props .Script , props .Direction , out .otMap .chosenScript [0 ])
zwm , fb := out .shaper .marksBehavior ()
out .scriptZeroMarks = zwm != zeroWidthMarksNone
out .scriptFallbackPosition = fb
if _ , isDefault := out .shaper .(complexShaperDefault ); out .applyMorx && !isDefault {
out .shaper = complexShaperDefault {dumb : true }
}
return &out
}
func (planner *otShapePlanner ) compile (plan *otShapePlan , key otShapePlanKey ) {
plan .props = planner .props
plan .shaper = planner .shaper
planner .otMap .compile (&plan .otMap , key )
if planner .applyMorx {
planner .aatMap .compile (&plan .aatMap )
}
plan .fracMask = plan .otMap .getMask1 (ot .NewTag ('f' , 'r' , 'a' , 'c' ))
plan .numrMask = plan .otMap .getMask1 (ot .NewTag ('n' , 'u' , 'm' , 'r' ))
plan .dnomMask = plan .otMap .getMask1 (ot .NewTag ('d' , 'n' , 'o' , 'm' ))
plan .hasFrac = plan .fracMask != 0 || (plan .numrMask != 0 && plan .dnomMask != 0 )
plan .rtlmMask = plan .otMap .getMask1 (ot .NewTag ('r' , 't' , 'l' , 'm' ))
plan .hasVert = plan .otMap .getMask1 (ot .NewTag ('v' , 'e' , 'r' , 't' )) != 0
kernTag := ot .NewTag ('v' , 'k' , 'r' , 'n' )
if planner .props .Direction .isHorizontal () {
kernTag = ot .NewTag ('k' , 'e' , 'r' , 'n' )
}
plan .kernMask , _ = plan .otMap .getMask (kernTag )
plan .requestedKerning = plan .kernMask != 0
hasGposKern := plan .otMap .getFeatureIndex (1 , kernTag ) != NoFeatureIndex
disableGpos := plan .shaper .gposTag () != 0 && plan .shaper .gposTag () != plan .otMap .chosenScript [1 ]
if planner .tables .GDEF .GlyphClassDef == nil {
plan .fallbackGlyphClasses = true
}
plan .applyMorx = planner .applyMorx
hasKerx := planner .tables .Kerx != nil
hasGSUB := !plan .applyMorx && planner .tables .GSUB .Lookups != nil
hasGPOS := !disableGpos && planner .tables .GPOS .Lookups != nil
if hasKerx && !(hasGSUB && hasGPOS ) {
plan .applyKerx = true
} else if hasGPOS {
plan .applyGpos = true
}
if !plan .applyKerx && (!hasGposKern || !plan .applyGpos ) {
if hasKerx {
plan .applyKerx = true
} else if planner .tables .Kern != nil {
plan .applyKern = planner .scriptFallbackPosition
}
}
plan .applyFallbackKern = planner .scriptFallbackPosition && !(plan .applyGpos || plan .applyKerx || plan .applyKern )
plan .zeroMarks = planner .scriptZeroMarks && !plan .applyKerx &&
(!plan .applyKern || !hasMachineKerning (planner .tables .Kern ))
plan .hasGposMark = plan .otMap .getMask1 (ot .NewTag ('m' , 'a' , 'r' , 'k' )) != 0
plan .adjustMarkPositioningWhenZeroing = !plan .applyGpos && !plan .applyKerx &&
(!plan .applyKern || !hasCrossKerning (planner .tables .Kern ))
plan .fallbackMarkPositioning = plan .adjustMarkPositioningWhenZeroing && planner .scriptFallbackPosition
if plan .applyMorx {
plan .adjustMarkPositioningWhenZeroing = false
}
plan .applyTrak = !planner .tables .Trak .IsEmpty () && planner .tables .STAT != nil
}
type otShapePlan struct {
shaper otComplexShaper
props SegmentProperties
otMap otMap
aatMap aatMap
fracMask GlyphMask
numrMask GlyphMask
dnomMask GlyphMask
rtlmMask GlyphMask
kernMask GlyphMask
hasFrac bool
requestedKerning bool
hasVert bool
hasGposMark bool
zeroMarks bool
fallbackGlyphClasses bool
fallbackMarkPositioning bool
adjustMarkPositioningWhenZeroing bool
applyGpos bool
applyFallbackKern bool
applyKern bool
applyKerx bool
applyMorx bool
applyTrak bool
}
func (sp *otShapePlan ) init0 (tables *font .Font , props SegmentProperties , userFeatures []Feature , otKey otShapePlanKey ) {
planner := newOtShapePlanner (tables , props )
planner .collectFeatures (userFeatures )
planner .compile (sp , otKey )
sp .shaper .dataCreate (sp )
}
func (sp *otShapePlan ) substitute (font *Font , buffer *Buffer ) {
sp .otMap .substitute (sp , font , buffer )
}
func (sp *otShapePlan ) position (font *Font , buffer *Buffer ) {
if sp .applyGpos {
sp .otMap .position (sp , font , buffer )
} else if sp .applyKerx {
sp .aatLayoutPosition (font , buffer )
}
if sp .applyKern {
sp .otLayoutKern (font , buffer )
} else if sp .applyFallbackKern {
sp .otApplyFallbackKern (font , buffer )
}
if sp .applyTrak {
sp .aatLayoutTrack (font , buffer )
}
}
var (
commonFeatures = [...]otMapFeature {
{ot .NewTag ('a' , 'b' , 'v' , 'm' ), ffGLOBAL },
{ot .NewTag ('b' , 'l' , 'w' , 'm' ), ffGLOBAL },
{ot .NewTag ('c' , 'c' , 'm' , 'p' ), ffGLOBAL },
{ot .NewTag ('l' , 'o' , 'c' , 'l' ), ffGLOBAL },
{ot .NewTag ('m' , 'a' , 'r' , 'k' ), ffGlobalManualJoiners },
{ot .NewTag ('m' , 'k' , 'm' , 'k' ), ffGlobalManualJoiners },
{ot .NewTag ('r' , 'l' , 'i' , 'g' ), ffGLOBAL },
}
horizontalFeatures = [...]otMapFeature {
{ot .NewTag ('c' , 'a' , 'l' , 't' ), ffGLOBAL },
{ot .NewTag ('c' , 'l' , 'i' , 'g' ), ffGLOBAL },
{ot .NewTag ('c' , 'u' , 'r' , 's' ), ffGLOBAL },
{ot .NewTag ('d' , 'i' , 's' , 't' ), ffGLOBAL },
{ot .NewTag ('k' , 'e' , 'r' , 'n' ), ffGlobalHasFallback },
{ot .NewTag ('l' , 'i' , 'g' , 'a' ), ffGLOBAL },
{ot .NewTag ('r' , 'c' , 'l' , 't' ), ffGLOBAL },
}
)
func (planner *otShapePlanner ) collectFeatures (userFeatures []Feature ) {
map_ := &planner .otMap
map_ .enableFeature (ot .NewTag ('r' , 'v' , 'r' , 'n' ))
map_ .addGSUBPause (nil )
switch planner .props .Direction {
case LeftToRight :
map_ .enableFeature (ot .NewTag ('l' , 't' , 'r' , 'a' ))
map_ .enableFeature (ot .NewTag ('l' , 't' , 'r' , 'm' ))
case RightToLeft :
map_ .enableFeature (ot .NewTag ('r' , 't' , 'l' , 'a' ))
map_ .addFeature (ot .NewTag ('r' , 't' , 'l' , 'm' ))
}
map_ .addFeature (ot .NewTag ('f' , 'r' , 'a' , 'c' ))
map_ .addFeature (ot .NewTag ('n' , 'u' , 'm' , 'r' ))
map_ .addFeature (ot .NewTag ('d' , 'n' , 'o' , 'm' ))
map_ .enableFeatureExt (ot .NewTag ('r' , 'a' , 'n' , 'd' ), ffRandom , otMapMaxValue )
map_ .enableFeature (ot .NewTag ('H' , 'a' , 'r' , 'f' ))
map_ .enableFeature (ot .NewTag ('H' , 'A' , 'R' , 'F' ))
planner .shaper .collectFeatures (planner )
map_ .enableFeature (ot .NewTag ('B' , 'u' , 'z' , 'z' ))
map_ .enableFeature (ot .NewTag ('B' , 'U' , 'Z' , 'Z' ))
for _ , feat := range commonFeatures {
map_ .addFeatureExt (feat .tag , feat .flags , 1 )
}
if planner .props .Direction .isHorizontal () {
for _ , feat := range horizontalFeatures {
map_ .addFeatureExt (feat .tag , feat .flags , 1 )
}
} else {
map_ .enableFeatureExt (ot .NewTag ('v' , 'e' , 'r' , 't' ), ffGlobalSearch , 1 )
}
for _ , f := range userFeatures {
ftag := ffNone
if f .Start == FeatureGlobalStart && f .End == FeatureGlobalEnd {
ftag = ffGLOBAL
}
map_ .addFeatureExt (f .Tag , ftag , f .Value )
}
planner .shaper .overrideFeatures (planner )
}
type otContext struct {
plan *otShapePlan
font *Font
buffer *Buffer
userFeatures []Feature
targetDirection Direction
}
func vertCharFor(u rune ) rune {
switch u >> 8 {
case 0x20 :
switch u {
case 0x2013 :
return 0xfe32
case 0x2014 :
return 0xfe31
case 0x2025 :
return 0xfe30
case 0x2026 :
return 0xfe19
}
case 0x30 :
switch u {
case 0x3001 :
return 0xfe11
case 0x3002 :
return 0xfe12
case 0x3008 :
return 0xfe3f
case 0x3009 :
return 0xfe40
case 0x300a :
return 0xfe3d
case 0x300b :
return 0xfe3e
case 0x300c :
return 0xfe41
case 0x300d :
return 0xfe42
case 0x300e :
return 0xfe43
case 0x300f :
return 0xfe44
case 0x3010 :
return 0xfe3b
case 0x3011 :
return 0xfe3c
case 0x3014 :
return 0xfe39
case 0x3015 :
return 0xfe3a
case 0x3016 :
return 0xfe17
case 0x3017 :
return 0xfe18
}
case 0xfe :
switch u {
case 0xfe4f :
return 0xfe34
}
case 0xff :
switch u {
case 0xff01 :
return 0xfe15
case 0xff08 :
return 0xfe35
case 0xff09 :
return 0xfe36
case 0xff0c :
return 0xfe10
case 0xff1a :
return 0xfe13
case 0xff1b :
return 0xfe14
case 0xff1f :
return 0xfe16
case 0xff3b :
return 0xfe47
case 0xff3d :
return 0xfe48
case 0xff3f :
return 0xfe33
case 0xff5b :
return 0xfe37
case 0xff5d :
return 0xfe38
}
}
return u
}
func (c *otContext ) otRotateChars () {
info := c .buffer .Info
if c .targetDirection .isBackward () {
rtlmMask := c .plan .rtlmMask
for i := range info {
codepoint := ucd .LookupMirrorChar (info [i ].codepoint )
if codepoint != info [i ].codepoint && c .font .hasGlyph (codepoint ) {
info [i ].codepoint = codepoint
} else {
info [i ].Mask |= rtlmMask
}
}
}
if c .targetDirection .isVertical () && !c .plan .hasVert {
for i := range info {
codepoint := vertCharFor (info [i ].codepoint )
if codepoint != info [i ].codepoint && c .font .hasGlyph (codepoint ) {
info [i ].codepoint = codepoint
}
}
}
}
func (c *otContext ) setupMasksFraction () {
if c .buffer .scratchFlags &bsfHasFractionSlash == 0 || !c .plan .hasFrac {
return
}
buffer := c .buffer
var preMask , postMask GlyphMask
if buffer .Props .Direction .isForward () {
preMask = c .plan .numrMask | c .plan .fracMask
postMask = c .plan .fracMask | c .plan .dnomMask
} else {
preMask = c .plan .fracMask | c .plan .dnomMask
postMask = c .plan .numrMask | c .plan .fracMask
}
count := len (buffer .Info )
info := buffer .Info
for i := 0 ; i < count ; i ++ {
if info [i ].codepoint == 0x2044 {
start , end := i , i +1
for start != 0 && info [start -1 ].unicode .generalCategory () == ucd .Nd {
start --
}
for end < count && info [end ].unicode .generalCategory () == ucd .Nd {
end ++
}
if start == i || end == i +1 {
if start == i {
buffer .unsafeToConcat (start , start +1 )
}
if end == i +1 {
buffer .unsafeToConcat (end -1 , end )
}
continue
}
buffer .unsafeToBreak (start , end )
for j := start ; j < i ; j ++ {
info [j ].Mask |= preMask
}
info [i ].Mask |= c .plan .fracMask
for j := i + 1 ; j < end ; j ++ {
info [j ].Mask |= postMask
}
i = end - 1
}
}
}
func (c *otContext ) initializeMasks () {
c .buffer .resetMasks (c .plan .otMap .globalMask )
}
func (c *otContext ) setupMasks () {
map_ := &c .plan .otMap
buffer := c .buffer
c .setupMasksFraction ()
c .plan .shaper .setupMasks (c .plan , buffer , c .font )
for _ , feature := range c .userFeatures {
if !(feature .Start == FeatureGlobalStart && feature .End == FeatureGlobalEnd ) {
mask , shift := map_ .getMask (feature .Tag )
buffer .setMasks (feature .Value <<shift , mask , feature .Start , feature .End )
}
}
}
func zeroWidthDefaultIgnorables(buffer *Buffer ) {
if buffer .scratchFlags &bsfHasDefaultIgnorables == 0 ||
buffer .Flags &PreserveDefaultIgnorables != 0 ||
buffer .Flags &RemoveDefaultIgnorables != 0 {
return
}
pos := buffer .Pos
for i , info := range buffer .Info {
if info .isDefaultIgnorable () {
pos [i ].XAdvance , pos [i ].YAdvance = 0 , 0
if buffer .Props .Direction .isHorizontal () {
pos [i ].XOffset = 0
} else {
pos [i ].YOffset = 0
}
}
}
}
func dealWithVariationSelectors(buffer *Buffer ) {
if (buffer .scratchFlags &bsfHasVariationSelectorFallback == 0 ) ||
buffer .notFoundVariationSelector == 0xFFFFFFFF {
return
}
info := buffer .Info
pos := buffer .Pos
for i := range info {
if info [i ].isVariationSelector () {
info [i ].Glyph = buffer .notFoundVariationSelector
pos [i ].XAdvance , pos [i ].YAdvance , pos [i ].XOffset , pos [i ].YOffset = 0 , 0 , 0 , 0
info [i ].setVariationSelector (false )
}
}
}
func hideDefaultIgnorables(buffer *Buffer , font *Font ) {
if buffer .scratchFlags &bsfHasDefaultIgnorables == 0 ||
buffer .Flags &PreserveDefaultIgnorables != 0 {
return
}
info := buffer .Info
var (
invisible = buffer .Invisible
ok bool
)
if invisible == 0 {
invisible , ok = font .face .NominalGlyph (' ' )
}
if buffer .Flags &RemoveDefaultIgnorables == 0 && ok {
for i := range info {
if info [i ].isDefaultIgnorable () {
info [i ].Glyph = invisible
}
}
} else {
otLayoutDeleteGlyphsInplace (buffer , (*GlyphInfo ).isDefaultIgnorable )
}
}
func synthesizeGlyphClasses(buffer *Buffer ) {
info := buffer .Info
for i := range info {
class := tables .GPMark
if info [i ].unicode .generalCategory () != ucd .Mn || info [i ].isDefaultIgnorable () {
class = tables .GPBaseGlyph
}
info [i ].glyphProps = class
}
}
func (c *otContext ) substituteBeforePosition () {
buffer := c .buffer
c .otRotateChars ()
otShapeNormalize (c .plan , buffer , c .font )
c .setupMasks ()
if c .plan .fallbackMarkPositioning {
fallbackMarkPositionRecategorizeMarks (buffer )
}
if debugMode {
fmt .Println ("BEFORE SUBSTITUTE:" , c .buffer .Info )
}
layoutSubstituteStart (c .font , buffer )
if c .plan .fallbackGlyphClasses {
synthesizeGlyphClasses (c .buffer )
}
if c .plan .applyMorx {
c .plan .aatLayoutSubstitute (c .font , c .buffer , c .userFeatures )
c .buffer .updateDigest ()
} else {
c .buffer .updateDigest ()
c .plan .substitute (c .font , buffer )
}
if c .plan .applyMorx && c .plan .applyGpos {
aatLayoutRemoveDeletedGlyphs (buffer )
}
}
func (c *otContext ) substituteAfterPosition () {
if c .plan .applyMorx && !c .plan .applyGpos {
aatLayoutRemoveDeletedGlyphs (c .buffer )
}
dealWithVariationSelectors (c .buffer )
hideDefaultIgnorables (c .buffer , c .font )
if debugMode {
fmt .Println ("POSTPROCESS glyphs start" )
}
c .plan .shaper .postprocessGlyphs (c .plan , c .buffer , c .font )
if debugMode {
fmt .Println ("POSTPROCESS glyphs end " )
}
}
func zeroMarkWidthsByGdef(buffer *Buffer , adjustOffsets bool ) {
for i , inf := range buffer .Info {
if inf .isMark () {
pos := &buffer .Pos [i ]
if adjustOffsets {
pos .XOffset -= pos .XAdvance
pos .YOffset -= pos .YAdvance
}
pos .XAdvance = 0
pos .YAdvance = 0
}
}
}
func (c *otContext ) positionDefault () {
direction := c .buffer .Props .Direction
info := c .buffer .Info
pos := c .buffer .Pos
if direction .isHorizontal () {
for i , inf := range info {
pos [i ].XAdvance , pos [i ].YAdvance = c .font .GlyphHAdvance (inf .Glyph ), 0
pos [i ].XOffset , pos [i ].YOffset = c .font .subtractGlyphHOrigin (inf .Glyph , 0 , 0 )
}
} else {
for i , inf := range info {
pos [i ].XAdvance , pos [i ].YAdvance = 0 , c .font .getGlyphVAdvance (inf .Glyph )
pos [i ].XOffset , pos [i ].YOffset = c .font .subtractGlyphVOrigin (inf .Glyph , 0 , 0 )
}
}
if c .buffer .scratchFlags &bsfHasSpaceFallback != 0 {
fallbackSpaces (c .font , c .buffer )
}
}
func (c *otContext ) positionComplex () {
info := c .buffer .Info
pos := c .buffer .Pos
adjustOffsetsWhenZeroing := c .plan .adjustMarkPositioningWhenZeroing && c .buffer .Props .Direction .isForward ()
for i , inf := range info {
pos [i ].XOffset , pos [i ].YOffset = c .font .addGlyphHOrigin (inf .Glyph , pos [i ].XOffset , pos [i ].YOffset )
}
otLayoutPositionStart (c .font , c .buffer )
markBehavior , _ := c .plan .shaper .marksBehavior ()
if c .plan .zeroMarks {
if markBehavior == zeroWidthMarksByGdefEarly {
zeroMarkWidthsByGdef (c .buffer , adjustOffsetsWhenZeroing )
}
}
c .plan .position (c .font , c .buffer )
if c .plan .zeroMarks {
if markBehavior == zeroWidthMarksByGdefLate {
zeroMarkWidthsByGdef (c .buffer , adjustOffsetsWhenZeroing )
}
}
zeroWidthDefaultIgnorables (c .buffer )
otLayoutPositionFinishOffsets (c .font , c .buffer )
for i , inf := range info {
pos [i ].XOffset , pos [i ].YOffset = c .font .subtractGlyphHOrigin (inf .Glyph , pos [i ].XOffset , pos [i ].YOffset )
}
if c .plan .fallbackMarkPositioning {
fallbackMarkPosition (c .plan , c .font , c .buffer , adjustOffsetsWhenZeroing )
}
}
func (c *otContext ) position () {
c .buffer .clearPositions ()
c .positionDefault ()
if debugMode {
fmt .Println ("AFTER DEFAULT POSITION" , c .buffer .Pos )
}
c .positionComplex ()
if c .buffer .Props .Direction .isBackward () {
c .buffer .Reverse ()
}
}
func propagateFlags(buffer *Buffer ) {
andMask := glyphFlagDefined
if buffer .Flags &ProduceUnsafeToConcat == 0 {
andMask &= ^GlyphUnsafeToConcat
}
info := buffer .Info
if buffer .Flags &ProduceSafeToInsertTatweel == 0 {
iter , count := buffer .clusterIterator ()
for start , end := iter .next (); start < count ; start , end = iter .next () {
if end -start == 1 {
info [start ].Mask &= andMask
continue
}
var mask GlyphMask
for i := start ; i < end ; i ++ {
mask |= info [i ].Mask
}
mask &= andMask
for i := start ; i < end ; i ++ {
info [i ].Mask = mask
}
}
return
}
iter , count := buffer .clusterIterator ()
for start , end := iter .next (); start < count ; start , end = iter .next () {
var mask GlyphMask
for i := start ; i < end ; i ++ {
mask |= info [i ].Mask
}
if mask &GlyphUnsafeToBreak != 0 {
mask &= ^GlyphSafeToInsertTatweel
}
if mask &GlyphSafeToInsertTatweel != 0 {
mask |= GlyphUnsafeToBreak | GlyphUnsafeToConcat
}
mask &= andMask
for i := start ; i < end ; i ++ {
info [i ].Mask = mask
}
}
}
type shaperOpentype struct {
tables *font .Font
plan otShapePlan
key otShapePlanKey
}
type otShapePlanKey = [2 ]int
func (sp *shaperOpentype ) init (tables *font .Font , coords []tables .Coord ) {
sp .plan = otShapePlan {}
sp .key = otShapePlanKey {
0 : tables .GSUB .FindVariationIndex (coords ),
1 : tables .GPOS .FindVariationIndex (coords ),
}
sp .tables = tables
}
func (sp *shaperOpentype ) compile (props SegmentProperties , userFeatures []Feature ) {
sp .plan .init0 (sp .tables , props , userFeatures , sp .key )
}
func (sp *shaperOpentype ) shape (font *Font , buffer *Buffer , features []Feature ) {
c := otContext {plan : &sp .plan , font : font , buffer : buffer , userFeatures : features }
c .buffer .scratchFlags = bsfDefault
const maxLenFactor = 64
const maxLenMin = 16384
const maxOpsFactor = 1024
const maxOpsMin = 16384
c .buffer .maxOps = max (len (c .buffer .Info )*maxOpsFactor , maxOpsMin )
c .buffer .maxLen = max (len (c .buffer .Info )*maxLenFactor , maxLenMin )
c .targetDirection = c .buffer .Props .Direction
c .initializeMasks ()
c .buffer .setUnicodeProps ()
c .buffer .insertDottedCircle (c .font )
c .buffer .formClusters ()
if debugMode {
fmt .Println ("FORMING CLUSTER :" , c .buffer .Info )
}
c .buffer .ensureNativeDirection ()
if debugMode {
fmt .Printf ("PREPROCESS text start\n" )
}
c .plan .shaper .preprocessText (c .plan , c .buffer , c .font )
if debugMode {
fmt .Println ("PREPROCESS text end:" , c .buffer .Info )
}
c .substituteBeforePosition ()
if debugMode {
fmt .Println ("AFTER SUBSTITUTE" , c .buffer .Info )
}
c .position ()
if debugMode {
fmt .Println ("AFTER POSITION" , c .buffer .Pos )
}
c .substituteAfterPosition ()
propagateFlags (c .buffer )
c .buffer .Props .Direction = c .targetDirection
c .buffer .maxOps = maxOpsDefault
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .